OpenNHP/opennhp · error
keystore: open database
Error message
keystore: open database %s: %w
What it means
NewAgentKeyStore wraps any error from sql.Open (driver registration / DSN parse) with this message. sql.Open for the 'sqlite' driver rarely touches disk — it fails mainly when the driver is not registered (blank import missing) or the DSN is malformed. The wrapped cause identifies which.
Solutions
- Verify the sqlite driver is imported for its side effect: _ "modernc.org/sqlite" (or the mattn variant).
- Check the DSN query params match the driver: _journal_mode=WAL&_busy_timeout=5000 works for modernc/mattn; other drivers differ.
- Confirm go.mod contains the sqlite driver module and the binary was rebuilt.
- Log the wrapped %w cause — it names the driver/DSN problem exactly.
Example fix
// before
import (
"database/sql"
)
// after
import (
"database/sql"
_ "modernc.org/sqlite"
) Defensive patterns
Strategy: validation
Validate before calling
// driver must be registered before opening
import _ "modernc.org/sqlite"
if _, err := sql.Open("sqlite", ":memory:"); err != nil {
return fmt.Errorf("sqlite driver unavailable: %w", err)
} Prevention
- Blank-import the sqlite driver in package main/init
- Keep DSN query params matched to the chosen driver
- Pin the sqlite driver version in go.mod
When it happens
Trigger: Calling NewAgentKeyStore with a driver name mismatch (e.g. modernc.org/sqlite vs mattn/go-sqlite3 DSN style), or when the sqlite driver was never imported so sql.Open returns 'sql: unknown driver'.
Common situations: Adding a dependency swap without updating the DSN query parameters (e.g. _journal_mode=WAL unsupported by the chosen driver), or building with a driver package accidentally dropped from go.mod.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- keystore: migrate
- keystore: get agent key
- keystore: sweep rows affected
- keystore: sweep otp rows affected
- keystore: check column
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/91a6412f87348d9e.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:42
// public key when the operator has not configured agentKeyTTLSeconds.
// 24 hours. Mirrors how OTPTTLSeconds is defaulted at the helper layer.
const DefaultAgentKeyTTLSeconds int64 = 86400
// NewAgentKeyStore opens (or creates) the SQLite database at dbPath.
// The directory is created if it does not exist.
func NewAgentKeyStore(dbPath string) (*AgentKeyStore, error) {
if dbPath == "" {
dbPath = filepath.Join("data", "nhp_server.db")
}
dir := filepath.Dir(dbPath)
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
}
View on GitHub (pinned to 6e04ca5ff0)