OpenNHP/opennhp · error

keystore: migrate

Error message

keystore: migrate: %w

What it means

NewAgentKeyStore fails when store.migrate() returns an error; it closes the DB and wraps the cause as 'keystore: migrate'. This is a coarse wrapper — the real reason (DDL failure, locked DB, schema conflict) is inside the wrapped error chain. Inspect errors.Unwrap / %v of the chain.

Solutions

  1. Inspect the wrapped cause: fmt.Sprintf("%+v", err) or errors.Unwrap to find the inner migrate/SQL error.
  2. Check directory and db file writability (needs 0700 dir, write access to db, -wal, -shm files).
  3. Ensure only one process owns the keystore at a time; the WAL busy_timeout is 5000ms and can be exceeded under contention.
  4. If the file is corrupt, back up and remove keystore.db (data loss) or use sqlite3 .recover.

Example fix

// before
store, err := NewAgentKeyStore(dir)
if err != nil { return err }
// after
store, err := NewAgentKeyStore(dir)
if err != nil {
    log.Error("keystore init failed: %v", err) // includes wrapped migrate cause
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure dir is writable before init
if info, err := os.Stat(dir); err != nil || info.Mode().Perm()&0700 == 0 {
    os.MkdirAll(dir, 0700)
}

Try / catch

store, err := NewAgentKeyStore(dir)
if err != nil {
    var inner error
    for e := err; e != nil; e = errors.Unwrap(e) { inner = e }
    log.Error("keystore init failed, root cause: %v", inner)
    return err
}

Prevention

When it happens

Trigger: First run against a corrupt or non-writable SQLite file, concurrent processes migrating simultaneously, or a migrate DDL statement failing (see errors 132/133 for the inner wrappers).

Common situations: Deploying two daemon instances pointed at the same keystore.db file, a read-only filesystem or directory permissions blocking WAL creation, or leftover -wal/-shm files from a crashed process.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/88ef94567ff91420. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/server/keystore.go:54

	if err := os.MkdirAll(dir, 0700); err != nil {
		return nil, fmt.Errorf("keystore: create directory %s: %w", dir, err)
	}

	db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
	if err != nil {
		return nil, fmt.Errorf("keystore: open database %s: %w", dbPath, err)
	}

	// Connection pool tuning — SQLite is single-writer; one open conn is
	// usually correct. Keep a small idle pool for concurrent read queries.
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(0)

	store := &AgentKeyStore{db: db}
	if err := store.migrate(); err != nil {
		db.Close()
		return nil, fmt.Errorf("keystore: migrate: %w", err)
	}

	log.Info("keystore: database opened at %s", dbPath)
	return store, nil
}

// Close closes the database connection.
func (s *AgentKeyStore) Close() error {
	return s.db.Close()
}

// migrate creates tables if they do not exist and applies incremental schema
// changes to existing databases.
func (s *AgentKeyStore) migrate() error {
	ddl := `
	CREATE TABLE IF NOT EXISTS otp_records (
		id         INTEGER PRIMARY KEY AUTOINCREMENT,
		usr_id     TEXT NOT NULL,

View on GitHub (pinned to 6e04ca5ff0)