Background-job sessions lack macOS Keychain/Security.framework access

Status Open
Reported on v2.1.238
Maintainer reply None cached
Activity 0 comments · opened Aug 21, 2026

Title: Background-job sessions lack macOS Keychain/Security.framework access

Environment

  • Claude Code CLI build: 2.1.238 (bug present; not yet tested on other builds)
  • OS: macOS (Darwin 25.6.0)
  • Session type: background job (claude --bg-pty-host ..., --fork-session --resume)
  • Two other background sessions were alive on the same machine at diagnosis time, on older builds (2.1.220, running ~9 days; 2.1.228, running ~1 day) — not tested, see "Not yet determined" below.

Summary

A Claude Code session running as a background job has no access to the macOS login Keychain or Security.framework APIs at all — not "access denied after a prompt," but a hard failure at the authorization layer, for every Keychain-backed or certificate-trust-evaluating operation. The identical operations succeed in a plain Terminal.app window and in a fresh interactive (non-background) Claude Code session on the same machine, same user, same login session.

This breaks any tool invoked from a background session that relies on OS Keychain storage or Security.framework certificate-trust evaluation.

Reproduction

Run each of the following in three contexts on the same machine/user:

security list-keychains
security add-generic-password -a "diag-test" -s "diag-test-svc" -w "x" -U

| Context | Result |
|---|---|
| Plain Terminal.app (no Claude session) | Works |
| Fresh interactive Claude Code session (non-background) | Works |
| Background-job Claude Code session (--bg-pty-host) | Fails |

Background-job session output:

$ security list-keychains
security: SecKeychainCopySearchList: One or more parameters passed to a function were not valid.

$ security add-generic-password -a "diag-test" -s "diag-test-svc" -w "x" -U
security: SecKeychainSearchCreateFromAttributes: One or more parameters passed to a function were not valid.
security: SecKeychainItemCreateFromContent (<default>): Unable to obtain authorization for this operation.
(exit 152)

list-keychains can't even enumerate which keychains exist — this isn't a permission prompt being denied, it's a process with no bootstrapped Keychain/Security-session context at all.

Root cause (as far as diagnosable from inside the affected session)

Process ancestry for the background-job session:

zsh (this session's shell)
 └─ claude (2.1.238, --session-id ..., --fork-session --resume ...)
     └─ claude --bg-pty-host /tmp/cc-daemon-.../pty/....sock ...
         └─ launchd (PID 1)

The background-job process tree is launched via a --bg-pty-host daemon mechanism rather than being a descendant of the interactive Terminal.app/login-window session. macOS gates Keychain and Security.framework access by the process's security/audit session — a process not bootstrapped into the user's Aqua (GUI login) session does not get access to the unlocked login Keychain, regardless of running as the correct Unix user with the correct HOME. That matches the observed symptoms: Keychain enumeration fails outright, and Keychain writes fail with an authorization error — both gated by the same missing session bootstrap, not by any application-level permission or prompt.

Not yet determined

Whether this is a recent regression (this machine had two other background sessions alive, on 2.1.220 and 2.1.228, that may or may not have working Keychain access right now — not tested) or whether background-job sessions have never had this access. The user reports past sessions did not exhibit this limitation, which would point to a regression, but this wasn't independently confirmed — testing it would have required asking a peer session to perform a Keychain write on the reporting user's behalf to compare, which was (correctly) declined as a permission-laundering pattern rather than attempted.

Impact

Any background-job session that needs to authenticate to a service backed by OS Keychain storage cannot do so, with no workaround available from inside the session itself. The task has to be handed to a human's terminal or a foreground interactive session instead.

View original on GitHub ↗