OpenNHP/opennhp · error
keystore: get agent key expiry
Error message
keystore: get agent key expiry: %w
What it means
GetAgentKeyExpiry wraps non-ErrNoRows failures of the SELECT active, expires_at lookup for a user+device row. Missing rows return (false, nil, nil); this error indicates an actual DB failure. Note the query here does not filter on active/expires_at in SQL — it reads the raw row — but a query error is still fatal and wrapped with the same style as the rest of the keystore.
Solutions
- Inspect the wrapped cause; a scan-type error means the schema drifted — migrate agent_keys to the expected column types.
- Verify agent_keys exists (run migrations) before plugins query expiry.
- Enable WAL mode / busy_timeout to avoid lock failures during concurrent sweeps.
- Confirm the keystore's DB handle is open for the lifetime of the plugin helper.
- Treat (false, nil, nil) as the correct 'not registered/expired' answer instead of conflating it with this error.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// caller precheck
if userId == "" || deviceId == "" { return errors.New("user and device required") } Type guard
active, exp, err := store.GetAgentKeyExpiry(user, dev)
if err != nil {
return fmt.Errorf("expiry lookup failed: %w", err)
}
// only inspect *exp when active == true Try / catch
active, exp, err := store.GetAgentKeyExpiry(u, d)
if err != nil {
log.Errorf("plugin expiry lookup: %v", err)
return active, exp, err
}
// (false, nil, nil) means missing/inactive/expired — not an error Prevention
- Migrate agent_keys schema (types of active/expires_at) before plugins load
- Initialize the keystore before plugin helpers run
- Enable WAL mode to survive concurrent sweeps
- Document the tri-state return so callers do not treat (false,nil,nil) as failure
When it happens
Trigger: Calling GetAgentKeyExpiry(userId, deviceId) from the plugin helper when the DB handle is closed, agent_keys is missing, the file is locked, or scanning the active/expires_at columns into int/sql.NullInt64 fails due to schema drift.
Common situations: Old DB files with different column types after an upgrade; plugin helper invoked before keystore initialization; SQLite lock contention during bulk sweeps.
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: get agent key
- keystore: find agent by pubkey
- keystore: check agent registered
- keystore: sweep expired
- keystore: sweep rows affected
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/5a548e776ea736cd.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:505
// (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 = ?`,
userId, deviceId,
).Scan(&active, &expiresAt)
if err == sql.ErrNoRows {
return false, nil, nil
}
if err != nil {
return false, nil, fmt.Errorf("keystore: get agent key expiry: %w", err)
}
if active != 1 {
return false, nil, nil
}
if expiresAt.Valid && expiresAt.Int64 <= time.Now().Unix() {
return false, nil, nil
}
if expiresAt.Valid {
ts := expiresAt.Int64
return true, &ts, nil
}
return true, nil, nil
}
// SweepExpiredDeactivates flips active=0 for any row whose expires_at has
// elapsed. Returns the number of rows updated. NULL expires_at rows are
// never swept (they are configured to never expire). The result of
// FindAgentByPublicKey / IsAgentRegistered does not depend on thisView on GitHub (pinned to 6e04ca5ff0)