[BUG] Claude Code CLI credential storage is OS-user-scoped, not Claude-account-scoped – causes cross-account credential bleed

Status Fixed / completed
Reported on v2.1.209
Maintainer reply None cached
Activity 0 comments · opened Jul 16, 2026 · closed Jul 19, 2026

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
login
), any Cowork/local-agent-mode background agent spawned for Account B appears to
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

  1. Run claude login in 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).

  1. In the Claude desktop app, sign in / switch to Account B and start a Cowork

(local-agent-mode) session.

  1. Inspect the spawned claude process (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.

  1. Inspect local-agent-mode-sessions/<accountB>/<orgB>/.claude.json — account fields

are empty/unpopulated, and no .credentials.json exists in that directory.

  1. 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.

View original on GitHub ↗