[macOS] Detached app-server repeatedly fails OAuth Keychain access with CSSMERR_CSP_NO_USER_INTERACTION
Codex version
0.147.0
Platform
macOS 26.6.1, Apple Silicon
Problem
Two Codex app-server processes remained alive after becoming detached from
their original interactive terminal context. Both had no TTY and were
reparented to PID 1.
During a later OAuth login attempt, the CLI reported:
failed to write OAuth tokens to keyring: Platform secure storage failure:
User interaction is not allowed.
macOS unified logs showed repeated:
CSSMERR_CSP_NO_USER_INTERACTION
The app-servers retried Keychain access approximately once per second. The
login Keychain itself was available and unlocked in the logged-in GUI session.
Neither detached server had an active client connection when inspected.
Gracefully terminating only those stale servers immediately stopped the
Keychain error loop and preserved the tmux session.
Observed conditions
- Start Codex app-server processes from an interactive macOS session.
- Allow their original owner/session to exit while the servers remain alive.
- The processes become detached, have no TTY, and are reparented to PID 1.
- Attempt an OAuth login or credential persistence operation.
- Keychain denies interaction and the app-servers repeatedly retry.
Expected behavior
- App-server lifecycle should remain tied to an explicit owner or durable
service context.
- A noninteractive Keychain failure should produce one actionable typed error
with bounded retry/backoff.
- OAuth persistence requiring user interaction should be routed through the
logged-in GUI context or deferred explicitly.
- Detached servers without clients should not retry Keychain access every
second.
This may be adjacent to #33540, but this report concerns detached app-server
lifecycle and noninteractive macOS Keychain access rather than refresh-token
serialization.
2 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
One structural observation from the OSS side (
main@ 1f41cc5d92) that explains why the retry loop is unbounded regardless of which poller drives it:The keyring layer has no error taxonomy.
codex-keyring-storewraps every failure in a single opaque variant —CredentialStoreError::Other(KeyringError)with only a stringified message (keyring-store/src/lib.rs#L9-L39). OnlyNoEntryis special-cased (asOk(None)on load). That meansCSSMERR_CSP_NO_USER_INTERACTION— which is a definitive answer ("this process has no user session; retrying cannot succeed until the execution context changes") — reaches every caller as an undifferentiated platform failure, indistinguishable from a transient hiccup. No caller up the stack (OAuth persistence, MCP auth-status polling, credential resolution) can implement "fail once with a typed error, stop retrying" because the information needed to make that decision is discarded at the lowest layer. Your observed ~1/s loop is then just whatever periodic auth/status machinery happens to sit on top, faithfully retrying an unretryable error.Fix shape:
keyring::Errorinto at least{NoEntry, NoUserInteraction/NoStorageAccess (terminal in this context), Other (possibly transient)}incodex-keyring-store, keeping the macOSCSSMERR_CSP_NO_USER_INTERACTIONmapping explicit. This is the enabling change — everything else is a consumer of it.The lifecycle half of your report (clientless app-servers surviving their owner) is a separate, valid issue — but note that even with perfect lifecycle management, any legitimately headless context (SSH session, launchd without Aqua, CI) hits the same untyped-error loop today, so the taxonomy fix stands on its own.