[macOS] Detached app-server repeatedly fails OAuth Keychain access with CSSMERR_CSP_NO_USER_INTERACTION

Open 💬 2 comments Opened Aug 15, 2026 by anupamchugh
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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

  1. Start Codex app-server processes from an interactive macOS session.
  2. Allow their original owner/session to exit while the servers remain alive.
  3. The processes become detached, have no TTY, and are reparented to PID 1.
  4. Attempt an OAuth login or credential persistence operation.
  5. 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.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 13 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #37969

Powered by Codex Action

jdcodes1 · 10 days ago

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-store wraps every failure in a single opaque variant — CredentialStoreError::Other(KeyringError) with only a stringified message (keyring-store/src/lib.rs#L9-L39). Only NoEntry is special-cased (as Ok(None) on load). That means CSSMERR_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:

  1. Taxonomy first: classify keyring::Error into at least {NoEntry, NoUserInteraction/NoStorageAccess (terminal in this context), Other (possibly transient)} in codex-keyring-store, keeping the macOS CSSMERR_CSP_NO_USER_INTERACTION mapping explicit. This is the enabling change — everything else is a consumer of it.
  2. Callers: on the terminal variant, emit one actionable error ("keychain unavailable: no user session — the app-server is running detached; restart it from an interactive session or use file-based credential storage") and suppress further attempts for the process lifetime (or until an auth-change event).
  3. Proactive guard: a detached process can detect its situation cheaply (no TTY + not in an Aqua session on macOS) and skip keychain access preemptively, falling back to the existing file-store mode with a warning — turning your scenario into a degraded-but-working state instead of a silent 1/s error loop.

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.