{"record":{"id":"88ef94567ff91420","repo":"OpenNHP/opennhp","slug":"keystore-migrate-w","errorCode":null,"errorMessage":"keystore: migrate: %w","messagePattern":"keystore: migrate: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/keystore.go","lineNumber":54,"sourceCode":"\tif err := os.MkdirAll(dir, 0700); err != nil {\n\t\treturn nil, fmt.Errorf(\"keystore: create directory %s: %w\", dir, err)\n\t}\n\n\tdb, err := sql.Open(\"sqlite\", dbPath+\"?_journal_mode=WAL&_busy_timeout=5000\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"keystore: open database %s: %w\", dbPath, err)\n\t}\n\n\t// Connection pool tuning — SQLite is single-writer; one open conn is\n\t// usually correct. Keep a small idle pool for concurrent read queries.\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(1)\n\tdb.SetConnMaxLifetime(0)\n\n\tstore := &AgentKeyStore{db: db}\n\tif err := store.migrate(); err != nil {\n\t\tdb.Close()\n\t\treturn nil, fmt.Errorf(\"keystore: migrate: %w\", err)\n\t}\n\n\tlog.Info(\"keystore: database opened at %s\", dbPath)\n\treturn store, nil\n}\n\n// Close closes the database connection.\nfunc (s *AgentKeyStore) Close() error {\n\treturn s.db.Close()\n}\n\n// migrate creates tables if they do not exist and applies incremental schema\n// changes to existing databases.\nfunc (s *AgentKeyStore) migrate() error {\n\tddl := `\n\tCREATE TABLE IF NOT EXISTS otp_records (\n\t\tid         INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\tusr_id     TEXT NOT NULL,","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/keystore.go#L36-L72","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Inspect the wrapped cause: fmt.Sprintf(\"%+v\", err) or errors.Unwrap to find the inner migrate/SQL error.","Check directory and db file writability (needs 0700 dir, write access to db, -wal, -shm files).","Ensure only one process owns the keystore at a time; the WAL busy_timeout is 5000ms and can be exceeded under contention.","If the file is corrupt, back up and remove keystore.db (data loss) or use sqlite3 .recover."],"exampleFix":"// before\nstore, err := NewAgentKeyStore(dir)\nif err != nil { return err }\n// after\nstore, err := NewAgentKeyStore(dir)\nif err != nil {\n    log.Error(\"keystore init failed: %v\", err) // includes wrapped migrate cause\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// ensure dir is writable before init\nif info, err := os.Stat(dir); err != nil || info.Mode().Perm()&0700 == 0 {\n    os.MkdirAll(dir, 0700)\n}","typeGuard":null,"tryCatchPattern":"store, err := NewAgentKeyStore(dir)\nif err != nil {\n    var inner error\n    for e := err; e != nil; e = errors.Unwrap(e) { inner = e }\n    log.Error(\"keystore init failed, root cause: %v\", inner)\n    return err\n}","preventionTips":["Run one daemon per keystore file","Keep keystore on local disk, never NFS","Monitor free disk space","Log the full error chain, not just the top wrapper"],"tags":["go","sqlite","migration","keystore"],"backgroundTag":"database-query-failed","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}