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

  1. Unwrap the error to read the driver message (no such table / database is locked / sql: database is closed).
  2. Ensure migrations create agent_keys with the usr_id/dev_id/active/expires_at columns before use.
  3. Configure SQLite WAL + busy_timeout to let reads proceed during writes.
  4. Check that registration handlers are not opening separate connections that lock the same file.
  5. 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

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


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)