OpenNHP/opennhp · error
keystore: insert agent key
Error message
keystore: insert agent key: %w
What it means
RegisterAgentKey performs an INSERT ... ON CONFLICT DO UPDATE (upsert) into agent_keys; failure is wrapped as 'keystore: insert agent key'. Causes include constraint violations, SQLITE_BUSY, disk I/O errors, or a driver not supporting the upsert syntax.
Solutions
- Check the wrapped error: 'syntax error near ON' means the driver's SQLite is too old — upgrade modernc.org/sqlite or mattn/go-sqlite3.
- Free disk space / verify volume writability (WAL needs db, -wal, -shm writes).
- Reduce cross-process write contention or serialize registrations.
- Re-run the registration; the upsert is idempotent for the same key.
Defensive patterns
Strategy: retry
Try / catch
err := store.RegisterAgentKey(u, d, pk, cs, ttl)
if err != nil && strings.Contains(err.Error(), "insert agent key") {
if strings.Contains(err.Error(), "syntax error") {
log.Fatal("sqlite driver too old for ON CONFLICT upsert; upgrade driver")
}
// bounded retry for transient BUSY
} Prevention
- Use a driver bundling SQLite >= 3.24 for upsert support
- Monitor disk space for WAL checkpoints
- Keep a single writer process per keystore file
When it happens
Trigger: Upsert Exec fails: UNIQUE constraint conflict beyond the handled idempotent path, WAL checkpoint I/O failure on a full disk, or old SQLite versions (<3.24) lacking ON CONFLICT DO UPDATE support.
Common situations: Embedded/older SQLite builds in the driver rejecting the upsert syntax ('near ON: syntax error'), disk-full containers, concurrent writers across processes.
Related errors
- keystore: query pubkey conflict
- keystore: check agent registered
- AuthServiceId is required
- no server cluster: set Cluster in resource.toml or use…
- registration failed
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/b21f89149427ab6d.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:416
expiresAt = sql.NullInt64{Int64: now + ttlSeconds, Valid: true}
}
// Upsert: insert or update on (usr_id, dev_id) conflict. Both fresh
// inserts and key rotations (including cipher scheme switches) reset
// the clock.
_, err = s.db.Exec(
`INSERT INTO agent_keys (usr_id, dev_id, public_key, cipher, created_at, expires_at, active)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(usr_id, dev_id) DO UPDATE SET
public_key = excluded.public_key,
cipher = excluded.cipher,
created_at = excluded.created_at,
expires_at = excluded.expires_at,
active = 1`,
userId, deviceId, pubKey, cipherScheme, now, expiresAt,
)
if err != nil {
return fmt.Errorf("keystore: insert agent key: %w", err)
}
log.Info("keystore: agent key registered for user=%s device=%s cipher=%d ttl=%ds", userId, deviceId, cipherScheme, ttlSeconds)
return nil
}
// 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 > ?)`,View on GitHub (pinned to 6e04ca5ff0)