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

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

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 named
Claude 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.json also 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 shows
CLAUDE_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

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

  1. Why is an existing valid entry not found/reused on relaunch — key derivation

mismatch, or a read that only checks the unsuffixed name?

  1. Should the desktop app and CLI share one credential source on macOS (as they

appear to attempt via CLAUDE_CODE_HOST_CREDS_FILE)?

  1. Old entries are never garbage-collected — add cleanup on successful login.

View original on GitHub ↗