OpenNHP/opennhp · error

keystore: query pubkey conflict

Error message

keystore: query pubkey conflict: %w

What it means

RegisterAgentKey probes for an existing key for the user+device; if the SELECT fails with anything other than ErrNoRows, it wraps as 'keystore: query pubkey conflict'. ErrNoRows is expected (new registration) — this wrapper signals a database-level failure.

Solutions

  1. Log and inspect the wrapped SQLite error code.
  2. Confirm the running binary matches the schema produced by migrate() — restart to run migrations.
  3. If SQLITE_BUSY, lower write concurrency or move to a multi-writer database backend.
  4. Run PRAGMA integrity_check if corruption is suspected.
Defensive patterns

Strategy: try-catch

Try / catch

if err := store.RegisterAgentKey(u, d, pk, cs, ttl); err != nil {
    if strings.Contains(err.Error(), "query pubkey conflict") {
        log.Error("registration db failure: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: The conflict-check SELECT fails: SQLITE_BUSY on contended writes, database corruption, or a table/column mismatch (agent_keys schema changed externally).

Common situations: Manual migrations or a stale binary running against a newer/older schema, crash-corrupted files, or read-only mounts failing mid-query under WAL.

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/fd5fa6f1b3bd4c2e. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/server/keystore.go:392

	now := time.Now().Unix()

	// Check for public key conflict (same key, different user/device).
	var existingUserId string
	err := s.db.QueryRow(
		`SELECT usr_id FROM agent_keys WHERE public_key = ? AND active = 1`,
		pubKey,
	).Scan(&existingUserId)
	if err == nil {
		if existingUserId != userId {
			return common.ErrPublicKeyAlreadyRegistered
		}
		// Same user, same key — idempotent, no-op. Do NOT reset the
		// expiry clock: a re-register attempt for the same key should
		// not extend an already-issued lifetime.
		return nil
	}
	if err != sql.ErrNoRows {
		return fmt.Errorf("keystore: query pubkey conflict: %w", err)
	}

	// Compute expires_at for this registration.
	var expiresAt sql.NullInt64
	if ttlSeconds > 0 {
		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,

View on GitHub (pinned to 6e04ca5ff0)