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

  1. Inspect the wrapped cause with errors.Unwrap / %v of the returned error to identify the driver failure (locked vs missing table vs closed DB).
  2. Verify the agent_keys table exists and migrations ran before the keystore is used.
  3. Check DB file permissions and that no other process holds an exclusive lock on the SQLite file.
  4. Ensure the keystore's *sql.DB is opened once and not closed by another code path while in use.
  5. 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

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


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 = 1

View on GitHub (pinned to 6e04ca5ff0)