macOS: Claude Code accumulates a new Keychain credential entry per login (110 in 5 weeks) and fails to reuse them, causing repeated forced re-auth
Claude Code accumulates a new macOS Keychain credential entry per login and fails to reuse existing ones, causing repeated forced re-authentication
Summary
On macOS, Claude Code stores OAuth credentials in Keychain items namedClaude Code-credentials-<hash> (plus a legacy unsuffixed Claude Code-credentials).
Over ~5 weeks a single machine accumulated 110 such items, growing by 2-6 per day.
The practical symptom: new terminal sessions frequently force a full re-login, and
the Claude Code CLI and the Claude desktop app never share authentication even though
both are signed into the same account. Each forced login appears to write a new
hash-suffixed Keychain item rather than updating an existing one, so the next launch
often can't find "its" credentials and prompts again.
Environment
- macOS 15 (Darwin 25.5.0), Apple Silicon
- Claude Code (terminal, native installer): 2.1.251,
~/.local/bin/claude -> ~/.local/share/claude/versions/2.1.251 - Claude Code (bundled in desktop app): 2.1.247,
~/Library/Application Support/Claude/claude-code/2.1.247/ - Claude desktop app: 1.40609.0
- Auth: Claude Pro subscription, OAuth (signed in via Google on the auth screen)
~/.claude/.credentials.jsonalso present and valid (dual storage)
Evidence
security dump-keychain | grep 'Claude Code-credentials' returned 110 entries:
Claude Code-credentials-1eb0243d
Claude Code-credentials-525493ee
Claude Code-credentials-4bfd337c
... (110 total, all svce="Claude Code-credentials-<8 hex>")
Creation timestamps (cdat) show steady accretion, accelerating recently:
2026-07-26 x2
2026-07-27 x2
2026-07-28 x2
2026-07-29 x2
2026-08-01 x2
2026-08-03 x1
2026-08-08 x2
2026-08-28 x3
2026-08-29 x10 (clusters of 3 created within the same second)
Three entries created in the same wall-clock second suggests multiple processes
(main session + child/SDK sessions spawned by the desktop app; env showsCLAUDE_CODE_CHILD_SESSION=1) each performing their own OAuth exchange and each
writing a distinct Keychain item.
Possible contributing factor: scope-dependent key
The credential blob stored on disk carries scopes:
user:file_upload, user:inference, user:mcp_servers, user:profile, user:sessions:claude_code
but the desktop-app-spawned Claude Code advertises (via CLAUDE_CODE_OAUTH_SCOPES):
user:inference user:file_upload user:profile user:sessions:claude_code (no user:mcp_servers)
If the -<hash> suffix is derived from the requested scope set / client identity
(the binary contains a CredentialKey symbol), then the CLI and the desktop app
would compute different keys, never share a credential, and each context would
re-create its own entry on every scope/client variation.
Expected
- A stable, bounded number of Keychain entries (ideally one per account).
- A login persists across new terminal sessions and across CLI/desktop until the
refresh token is actually revoked or expired.
- CLI and desktop app on the same account either share credentials or at minimum
don't repeatedly invalidate each other.
Actual
- Unbounded growth of Keychain items, one (or several) per login.
- Frequent forced re-authentication when opening a new terminal session.
- CLI and desktop never share auth despite same account.
Impact
- User-visible: constant "log back in" prompts, described by the user as the single
biggest friction in daily use.
- Keychain pollution: 110 stale secret items on one machine in 5 weeks.
- Possible refresh-token churn: if stale entries hold superseded refresh tokens and a
process picks one up, rotating-refresh-token semantics would force re-login.
Workarounds found
claude setup-token+export CLAUDE_CODE_OAUTH_TOKEN=...in shell profile stops
terminal re-prompts (bypasses Keychain).
- Manual cleanup: back up then
security dump-keychain | grep -oE '"Claude Code-credentials-[0-9a-f]{8}"' | tr -d '"' | sort -u | while read s; do security delete-generic-password -s "$s"; done
Suggested investigation
- What exactly is hashed into the
Claude Code-credentials-<hash>suffix? Should it
be stable per account rather than per scope-set / per invocation context?
- Why is an existing valid entry not found/reused on relaunch — key derivation
mismatch, or a read that only checks the unsuffixed name?
- Should the desktop app and CLI share one credential source on macOS (as they
appear to attempt via CLAUDE_CODE_HOST_CREDS_FILE)?
- Old entries are never garbage-collected — add cleanup on successful login.