[Bug][macOS] Killing a blocked `security` Keychain read after 5s crashes securityd and causes an endless login-keychain prompt loop

Aperta
#8,161 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
macos, typescript

Direzione di ricerca

Start with src/oauth/local-token-detect.ts and src/claude/auth-detect.ts, then trace how their security child processes handle the 5 s timeout. Reproduce with a locked login keychain and check whether the reads trigger repeated prompts or securityd crashes. Done when these reads avoid the reported crash loop and retain their existing fallback behavior.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

account-pool bug platform service
Client or integration

Claude Code

Area

Authentication and account pool

Summary

On macOS 27.0, the Claude Code Keychain reads in src/oauth/local-token-detect.ts (readClaudeKeychain, readClaudeKeychainAsync) and the probe in src/claude/auth-detect.ts (keychainProbe) run /usr/bin/security with a 5 s timeout. When the login keychain is locked, security blocks on the SecurityAgent unlock prompt, and the timeout kills it while the prompt is still open.

Killing a security process while its SecurityAgent query is pending hits a race in Apple's securityd: it logs Probable bug: error destroying Mutex: 16 / UNIX error exception: 16 and aborts (SIGABRT, std::terminate). When securityd restarts, the login keychain is locked again. The next request reads the keychain, gets killed after 5 s, and crashes securityd again, so the user sees an endless "security wants to use the login keychain" password prompt.

I expected OpenCodex not to kill a security that is blocked on a prompt, or to skip the read when the login keychain is locked.

The securityd race itself is Apple's bug, but other tools have hit the same trigger and stopped the loop by no longer killing blocked security calls:

Reproduction

Intermittent, because it needs the login keychain to be locked first (one securityd crash, or a keychain whose password doesn't match the login password).

  1. Run ocx as the launchd service with Claude Code signed in through the macOS Keychain.
  2. Get the login keychain locked (for example after one securityd crash).
  3. Send Claude requests through the proxy. Each read spawns security find-generic-password -s "Claude Code-credentials" -w, which blocks on the unlock prompt and is killed after 5 s.
  4. securityd crashes, the keychain locks again, and the prompts keep coming back.

What I observed: 62 security runs in 7 minutes, 16 of them with no normal exit, and 4 securityd crash reports (/Library/Logs/DiagnosticReports/securityd-*.ips), all with the same faulting MachServer stack.

Version

2.97.0 (2.96.0 behaves the same)

Operating system

macOS 27.0 (26A428), Apple Silicon

Provider and model

Claude (claude-cli adapter, Keychain credentials)

Logs or error output
securityd: [com.apple.securityd:SecurityAgentXPCQuery] SecurityAgentXPCQuery(...) dying
securityd: [com.apple.securityd:SecError] Probable bug: error destroying Mutex: 16
securityd: [com.apple.securityd:security_exception] UNIX error exception: 16
crash: EXC_CRASH (SIGABRT), abort() called, faulting thread "MachServer", std::terminate
Screenshots and supporting files

The prompt that keeps coming back: "security wants to use the 'login' keychain. Enter the keychain password."

Redacted configuration

Default service install; nothing relevant in the config.

Possible fix

The workaround I'm running locally checks the login keychain lock state without showing any UI (SecKeychainOpen + SecKeychainGetStatus through bun:ffi) and skips the security call when it is locked. The read returns null (or "unknown" for the probe) and falls back as it already does on a timeout. If anything fails, it returns "not locked", so the behaviour stays the same as now.

export function loginKeychainLocked(path = join(homedir(), "Library", "Keychains", "login.keychain-db")): boolean {
  if (process.platform !== "darwin") return false;
  // dlopen Security.framework: SecKeychainOpen, SecKeychainGetStatus; CoreFoundation: CFRelease
  // return (status & kSecUnlockStateStatus) === 0; any error -> false
}

Then call it at the start of readClaudeKeychain, readClaudeKeychainAsync and keychainProbe. Another option is to stop killing a blocked child: resolve null after 5 s but let the child exit on its own, and share a single in-flight read.

readMuseKeychain already waits 120 s for an interactive login, so I left it alone.

Checks
  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.
Lingua principale
TypeScript
Stelle
16.9k
Fork
1.3k
Merge medio
20h 9m
PR unite (30g)
690

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di lidge-jun/opencodex

Tutte le issue di lidge-jun/opencodex

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.