{"record":{"id":"8889446c7d5385a6","repo":"OpenNHP/opennhp","slug":"keystore-check-column-s-s-w","errorCode":null,"errorMessage":"keystore: check column %s.%s: %w","messagePattern":"keystore: check column (.+?)\\.(.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/keystore.go","lineNumber":118,"sourceCode":"\t}\n\n\t// Incremental migrations: add columns that may be absent in older databases.\n\tmigrations := []struct {\n\t\ttable  string\n\t\tcolumn string\n\t\tddl    string\n\t}{\n\t\t{\"otp_records\", \"pub_key\", \"ALTER TABLE otp_records ADD COLUMN pub_key TEXT NOT NULL DEFAULT ''\"},\n\t\t{\"otp_records\", \"attempts\", \"ALTER TABLE otp_records ADD COLUMN attempts INTEGER DEFAULT 0\"},\n\t}\n\tfor _, m := range migrations {\n\t\tvar exists int\n\t\terr := s.db.QueryRow(\n\t\t\t`SELECT COUNT(*) FROM pragma_table_info(?) WHERE name = ?`,\n\t\t\tm.table, m.column,\n\t\t).Scan(&exists)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"keystore: check column %s.%s: %w\", m.table, m.column, err)\n\t\t}\n\t\tif exists == 0 {\n\t\t\tif _, err := s.db.Exec(m.ddl); err != nil {\n\t\t\t\treturn fmt.Errorf(\"keystore: migrate %s.%s: %w\", m.table, m.column, err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ── OTP operations ────────────────────────────────────────────────────────\n\n// OTPCooldownSeconds is the minimum interval between successive OTP\n// generations for the same user+device. A request that arrives before\n// the cooldown elapses is rejected with ErrOTPCooldown.\nconst OTPCooldownSeconds int64 = 60\n","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/keystore.go#L100-L136","documentation":"migrate() checks whether an expected column exists via pragma_table_info; if that SELECT fails it wraps the scan error with this message. It means the pragma query itself failed (SQLite error or Scan type mismatch), not that the column is missing.","triggerScenarios":"pragma_table_info unavailable (very old SQLite amalgamation embedded in the driver), the table name binding fails, or a rows/Scan error such as scanning COUNT(*) into a non-int variable.","commonSituations":"Driver with an old bundled SQLite lacking pragma table-valued functions; transient SQLITE_BUSY on a contended WAL database exceeding the 5000ms busy timeout.","solutions":["Read the wrapped cause for the specific SQLite error code (e.g. SQLITE_BUSY, SQLITE_LOCKED).","If SQLITE_BUSY, reduce concurrency or raise _busy_timeout in the DSN.","Verify the driver's bundled SQLite supports pragma_table_info (SQLite >= 3.16).","Retry once after a short backoff for transient lock errors."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"store, err := NewAgentKeyStore(dir)\nif err != nil && strings.Contains(err.Error(), \"check column\") {\n    time.Sleep(250 * time.Millisecond)\n    store, err = NewAgentKeyStore(dir) // retry once for transient BUSY\n}","preventionTips":["Use a driver with SQLite >= 3.16 (pragma_table_info)","Keep single-writer ownership of the db file","Raise _busy_timeout for contended deployments"],"tags":["go","sqlite","migration","pragma"],"backgroundTag":"sql-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"}