OpenNHP/opennhp · error
keystore: get agent key
Error message
keystore: get agent key: %w
What it means
AgentKeyStore.GetAgentKey wraps any non-ErrNoRows failure from the QueryRow lookup of an agent's public key in the agent_keys table. sql.ErrNoRows is treated as a legitimate 'not found' (returns nil, nil); this error means the query itself failed — DB closed, table missing, driver error, or scan type failure. The underlying driver error is preserved via %w so callers can inspect it with errors.As/Is.
Solutions
- Inspect the wrapped cause with errors.Unwrap / %v of the returned error to identify the driver failure (locked vs missing table vs closed DB).
- Verify the agent_keys table exists and migrations ran before the keystore is used.
- Check DB file permissions and that no other process holds an exclusive lock on the SQLite file.
- Ensure the keystore's *sql.DB is opened once and not closed by another code path while in use.
- Enable SQLite busy_timeout / WAL mode to tolerate concurrent access.
Example fix
// before
rec, err := store.GetAgentKey(user, dev)
if err != nil { return err }
// after
rec, err := store.GetAgentKey(user, dev)
if err != nil {
log.Errorf("agent key lookup failed for %s/%s: %v", user, dev, err) // logged cause is the wrapped driver error
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
if err := db.Ping(); err != nil { return fmt.Errorf("keystore db unavailable: %w", err) } Type guard
rec, err := store.GetAgentKey(user, dev)
if err != nil {
var derr *driverError
if errors.As(err, &derr) { /* handle driver failure */ }
return err
}
// rec == nil means not registered (not an error) Try / catch
rec, err := store.GetAgentKey(userId, deviceId)
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
// retry with backoff
}
return fmt.Errorf("agent key lookup unavailable: %w", err)
} Prevention
- Run schema migrations at startup before any keystore call
- Enable WAL mode and busy_timeout on the SQLite handle
- Never close the shared *sql.DB while the server is serving
- Distinguish nil-rec (not found) from err (DB failure) at every call site
When it happens
Trigger: Calling GetAgentKey(userId, deviceId) when the SQLite/database handle is closed or corrupted, the agent_keys table does not exist (schema not migrated), the DB file is locked by another process beyond the busy timeout, or a column type cannot be scanned into the expected fields.
Common situations: Server started before migrations ran; the DB file was deleted or moved while nhp-serverd was running; read-only filesystem; concurrent writers hitting SQLite 'database is locked'; misconfigured DSN in config.toml.
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
- keystore: open database
- keystore: migrate
- keystore: find agent by pubkey
- keystore: check agent registered
- keystore: get agent key expiry
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/87abc0c4ea6f4075.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:441
// GetAgentKey returns the public key for a given user+device, or nil if
// not found OR if the row is past its expires_at. Expired rows are
// indistinguishable from never-registered ones to all callers.
func (s *AgentKeyStore) GetAgentKey(userId, deviceId string) (*AgentKeyRecord, error) {
rec := &AgentKeyRecord{}
var expiresAt sql.NullInt64
var active int
err := s.db.QueryRow(
`SELECT usr_id, dev_id, public_key, cipher, created_at, expires_at, active
FROM agent_keys
WHERE usr_id = ? AND dev_id = ? AND active = 1
AND (expires_at IS NULL OR expires_at > ?)`,
userId, deviceId, time.Now().Unix(),
).Scan(&rec.UserId, &rec.DeviceId, &rec.PublicKey, &rec.Cipher, &rec.CreatedAt, &expiresAt, &active)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("keystore: get agent key: %w", err)
}
rec.Active = active == 1
if expiresAt.Valid {
rec.ExpiresAt = &expiresAt.Int64
}
return rec, nil
}
// FindAgentByPublicKey returns true if the given base64-encoded public key
// is registered, active, and not expired. This is the gate consulted by
// the noise-layer peer validation fallback; an expired key behaves as if
// the agent were never registered.
func (s *AgentKeyStore) FindAgentByPublicKey(pubKeyBase64 string) (bool, error) {
var count int
err := s.db.QueryRow(
`SELECT COUNT(*) FROM agent_keys
WHERE public_key = ? AND active = 1View on GitHub (pinned to 6e04ca5ff0)