[BUG] Claude Code CLI credential storage is OS-user-scoped, not Claude-account-scoped – causes cross-account credential bleed
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Environment
- macOS (Darwin 25.5.0)
- claude-code CLI: 2.1.209 (bundled inside Claude desktop app) and 2.1.211 (VS Code extension: anthropic.claude-code-2.1.211-darwin-arm64)
- Claude desktop app version: 1.21459.0
Summary
The Claude Code CLI stores its OAuth credential in a single macOS Keychain entry named
"Claude Code-credentials", keyed only to the OS username (acct = local Unix username),
not to which Claude.ai account initiated login. Every invocation of the claude binary
on the machine — VS Code extension, terminal, and the Claude desktop app's embedded
Cowork/local-agent-mode process — reads from this same single Keychain slot.
Separately, the desktop app does create per-account session directories under~/Library/Application Support/Claude/local-agent-mode-sessions/<accountUuid>/<orgUuid>/
with their own .claude.json recording the intended account's email/UUID — but no
corresponding .credentials.json (or any per-account token store) exists alongside it.
So the account metadata is isolated per-session, but the actual bearer token is not.
Impact
If a user is logged into the desktop app's chat UI as Account B, but the machine's
shared Keychain credential belongs to Account A (e.g. from a VS Code/terminal claude), any Cowork/local-agent-mode background agent spawned for Account B appears to
login
fall back to Account A's token for actual API calls — even though its local config
metadata correctly names Account B. This means:
- Token usage/billing for Account B's Cowork sessions may actually post against Account A.
- A long-running/unattended Cowork agent under one login can silently consume another
account's usage quota with no visible indication in the desktop app UI.
What Should Happen?
Each Claude account used by the desktop app (especially for Cowork/local-agent-mode)
should have its own isolated credential store (e.g. its own Keychain entry or.credentials.json inside its session directory), so background agents authenticate
and bill against the account that actually launched them — not whichever account most
recently ran claude login elsewhere on the machine.
Error Messages/Logs
Steps to Reproduce
- Run
claude loginin a terminal or VS Code as Account A. Confirm via
security find-generic-password -s "Claude Code-credentials" that the Keychain
entry exists (attributes only, not the secret).
- In the Claude desktop app, sign in / switch to Account B and start a Cowork
(local-agent-mode) session.
- Inspect the spawned
claudeprocess (ps aux | grep claude-code) — note it carries
--plugin-dir .../local-agent-mode-sessions/skills-plugin/<orgB>/<accountB>, i.e. it
believes it's scoped to Account B.
- Inspect
local-agent-mode-sessions/<accountB>/<orgB>/.claude.json— account fields
are empty/unpopulated, and no .credentials.json exists in that directory.
- There is no separate Keychain entry for Account B; only the single
"Claude Code-credentials" entry (scoped to the OS user) exists.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
1.21459.0
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Discovered while investigating unexpectedly high token usage from a Cowork session that
ran unattended for ~23 hours (--effort max, --model claude-opus-4-8,--permission-mode acceptEdits --allow-dangerously-skip-permissions) against a large
(~34MB / 8,395-line) session transcript, re-reading ~578K cached tokens per turn.