Headless `claude -p` from a non-default-account session uses the machine-default Keychain credential (silent cross-account billing)
Bug: headless claude -p ignores the session's account and uses the machine-default Keychain credential (silently bills the wrong account)
Environment
- Claude Code version: 2.1.195
- OS: macOS (Darwin)
- Auth: claude.ai first-party OAuth (no
ANTHROPIC_API_KEYset) - Two accounts on one machine via separate Claude Desktop instances:
- Default
Claude.app→ Account P (personal subscription) - Second instance:
open -n -a "/Applications/Claude.app" --args --user-data-dir="$HOME/.claude-instances/work"→ Account E (enterprise org) - The active Claude Code session runs as a child of the Account E Desktop instance.
Summary
From a Claude Code session authenticated as Account E, spawning a fresh headlessclaude -p … (e.g. via the Bash tool or any shell) authenticates as Account P —
the machine-default OAuth token in the macOS Keychain — not Account E. The mismatch is
silent, and claude auth status reports Account E, masking it. Usage, billing, and
rate limits are charged to the wrong account.
Steps to reproduce
- Log the default
Claude.app/ defaultclaudeCLI into Account P (writes the
OAuth token to Keychain service Claude Code-credentials).
- Launch a second instance into a separate data dir, logged into Account E:
open -n -a "/Applications/Claude.app" --args --user-data-dir="$HOME/.claude-instances/work"
- Start a Claude Code session inside the Account E instance.
- From that session run a headless command, e.g.
claude -p "say hi" --output-format json. - Observe which account is billed / rate-limited.
Expected
The headless claude -p subprocess authenticates as the session's account
(Account E), or fails loudly / warns on credential ambiguity.
Actual
- The subprocess authenticates as Account P (machine-default Keychain token).
claude auth statusreports Account E (it reads cached~/.claude.json
oauthAccount metadata), so it does not reflect the credential actually used for
inference.
- Account P hit its session/rate limit; Account E's usage meter did not change —
confirming the calls billed Account P.
Diagnosis (observed)
- Token actually used for inference: macOS Keychain item
service: "Claude Code-credentials"
(the machine default = Account P).
~/.claude.jsonoauthAccountmetadata = Account E (written because the Account E
instance shares $HOME); this is what claude auth status displays.
- The session carries host-auth env signals (
CLAUDE_CODE_CHILD_SESSION,
CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH), indicating it received Account E auth
in-memory from the host instance — but a newly spawned top-level claude process does
not inherit that and falls back to the Keychain default.
Impact
- Silent cross-account billing / rate-limit consumption: a user working in an
enterprise-authenticated session can unknowingly spend a personal account's allotment
(or vice versa). Especially dangerous for headless/automated workflows launched from
within a session.
claude auth statusis misleading — it shows the metadata account, not the credential
claude -p actually uses.
Suggested fixes
- Child
claudeinvocations should inherit the parent session's resolved credential
(credential/env handoff), or
claude -pshould honor an explicit account selection and **warn when the session
account ≠ the credential it will use**, and
claude auth statusshould report the credential actually used for inference (the
Keychain token's account), not just ~/.claude.json metadata.
Workaround
Set an explicit credential for headless runs to bypass the ambiguous Keychain default:ANTHROPIC_API_KEY=… claude -p …
Possibly related (but distinct) existing issues
- #70124 — Stored
/logincredentials silently beat a validCLAUDE_CODE_OAUTH_TOKEN
(auth-precedence theme).
- #62338 — Claude Code silently billed API instead of Max subscription, then gave
incorrect auth confirmation (silent mis-billing + misleading auth status).
- #68501 — Both Claude Desktop + standalone CLI installed → subscription usage silently
billed to Extra Usage (Desktop + CLI billing interaction).
This report differs: it is specifically about a claude -p child subprocess using the
machine-default Keychain credential instead of the account the parent session is
authenticated as, when two Desktop instances run under separate --user-data-dirs — withclaude auth status reporting the wrong (metadata) account and masking it.