OpenNHP/opennhp · error
keystore: check agent registered
Error message
keystore: check agent registered: %w
What it means
IsAgentRegistered wraps any QueryRow failure when counting active, non-expired keys for a user+device pair. Like the other lookups, an empty result is not an error — only genuine DB failures (closed handle, missing table, lock contention, driver faults) produce this wrapped error.
Solutions
- Unwrap the error to read the driver message (no such table / database is locked / sql: database is closed).
- Ensure migrations create agent_keys with the usr_id/dev_id/active/expires_at columns before use.
- Configure SQLite WAL + busy_timeout to let reads proceed during writes.
- Check that registration handlers are not opening separate connections that lock the same file.
- Verify filesystem health and free space on the DB volume.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
var exists int
if err := db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='agent_keys'").Scan(&exists); err != nil || exists == 0 {
return errors.New("agent_keys table missing; run migrations")
} Type guard
registered, err := store.IsAgentRegistered(user, dev)
if err != nil {
log.Errorf("registration check failed: %v", err)
return false, err
} Try / catch
ok, err := store.IsAgentRegistered(u, d)
if err != nil {
if errors.Is(err, sql.ErrConnDone) || strings.Contains(err.Error(), "closed") {
// reinitialize keystore
}
return false, err
} Prevention
- Serialize registration writes or use WAL so counts are not blocked
- Add a startup self-check that queries agent_keys once
- Keep the DB on local storage, not network mounts
- Distinguish (false, nil) from error paths in callers
When it happens
Trigger: Calling IsAgentRegistered(userId, deviceId) during registration or knock validation while the DB is locked by a writer, the table is absent, or the connection has been closed.
Common situations: Concurrent registration writes blocking the count query; server started with a fresh config pointing at a nonexistent DB file without migrations; disk I/O errors on the host.
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: query pubkey conflict
- keystore: insert agent key
- keystore: get agent key
- keystore: find agent by pubkey
- keystore: get agent key expiry
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/807324c5c2fc90ce.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:480
).Scan(&count)
if err != nil {
return false, fmt.Errorf("keystore: find agent by pubkey: %w", err)
}
return count > 0, nil
}
// IsAgentRegistered returns true if the user+device pair has an active,
// non-expired registered key.
func (s *AgentKeyStore) IsAgentRegistered(userId, deviceId string) (bool, error) {
var count int
err := s.db.QueryRow(
`SELECT COUNT(*) 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(&count)
if err != nil {
return false, fmt.Errorf("keystore: check agent registered: %w", err)
}
return count > 0, nil
}
// GetAgentKeyExpiry returns the expiry status for the given user+device:
//
// (true, &ts, nil) — row exists and is active with expires_at = ts
// (true, nil, nil) — row exists and is active with no expiry (NULL)
// (false, nil, nil) — row is missing, deactivated, or already expired
//
// Used by the plugin helper to surface "valid until when?" without
// reaching into the keystore itself. The third return value is reserved
// for future I/O errors; today it is always nil when the lookup ran.
func (s *AgentKeyStore) GetAgentKeyExpiry(userId, deviceId string) (bool, *int64, error) {
var active int
var expiresAt sql.NullInt64
err := s.db.QueryRow(
`SELECT active, expires_at FROM agent_keys WHERE usr_id = ? AND dev_id = ?`,View on GitHub (pinned to 6e04ca5ff0)