[BUG] Credential storage: refresh token written in plaintext, and Keychain items accumulate without bound

Status Open
Reported on v2.1.212
Maintainer reply None cached
Activity 1 comment · opened Sep 1, 2026

What's Wrong?

Two related credential-storage problems on macOS, plus a scope/auth-mode issue.

  1. PLAINTEXT REFRESH TOKEN. ~/.claude/.credentials.json (mode 0600) contains a live

access token and a refresh token valid for 27 days. A Keychain item requires
per-application ACL consent; a 0600 file requires only executing as the user. The
Keychain is in use simultaneously (see 2), so this is not a fallback for a missing
platform store.

  1. UNBOUNDED KEYCHAIN ACCUMULATION. The login keychain holds 1,306 items with service

prefix Claude Code-credentials out of 1,440 total services — 90.6% of the keychain.
Suffixes are uniformly 8 lowercase hex chars with no other length present, indicating
one generating code path. Observed growing live during a single session:
1,295 -> 1,298 -> 1,300 -> 1,306. Not correlated with sessions
(68 session-env dirs, 2 sessions dirs).

  1. SCOPE DOWNGRADE ON RE-AUTH. The authorize URL requests scope=user%3Ainference

(one scope) while the stored credential carries five, including
user:sessions:claude_code. Completing that flow yields fewer capabilities than the
credential it replaces, and drops the scope claude --teleport requires — so the
offered login cannot fix the failure that prompted it. No warning is shown.

  1. AUTH MODE MISREPORTED. claude --teleport <id> returns "API key authentication is

not sufficient" while a valid Max OAuth credential with the required scope is present
and no API key is configured anywhere.

What Should Happen?

  1. Refresh tokens stored in the Keychain, not plaintext, when the Keychain is available.
  2. Superseded credential items removed; item count bounded.
  3. Re-auth requests at least the scopes the existing credential holds, or warns explicitly.
  4. Auth mode reported accurately based on actual credential state.

Error Messages / Logs

$ claude --teleport session_01EWeDKeB3S5mWCn92jWJar4
Claude Code web sessions require authentication with a Claude.ai account. API
key authentication is not sufficient. Please run /login to authenticate, or
check your authentication status with /status.

Machine state at that same moment:
accessToken valid (expires 2026-09-01T11:31:05Z)
refreshToken valid (expires 2026-09-28T20:09:46Z)
subscription max
scopes user:file_upload user:inference user:mcp_servers user:profile
user:sessions:claude_code
ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / CLAUDE_CODE_OAUTH_TOKEN : all unset
(verified: env -i HOME=$HOME /bin/zsh -lic, plus .zshrc .zshenv .zprofile
.profile .bashrc, plus settings.json and settings.local.json)

Steps to Reproduce

  1. Authenticate normally with a Claude.ai (Max) account on macOS.
  2. Observe the plaintext store exists alongside the Keychain:

ls -l ~/.claude/.credentials.json

  1. Count credential items:

security dump-keychain | grep -c '"svce"<blob>="Claude Code-credentials'

  1. Use Claude Code normally for a period; re-run step 3. The count increases and

superseded items are never removed.

  1. Run claude --teleport <any-session-id>. Observe the API-key message despite a

valid OAuth credential.

  1. Start /login and inspect the authorize URL: scope=user%3Ainference only.

Additional Information

MEASURED, NEGATIVE — Keychain performance is NOT degraded at this item count, so this
is a hygiene and blast-radius issue, not a performance one. SecItemCopyMatching
(attributes only, 25 iterations): exact-service lookup median 0.258 ms against a
0.135 ms nonexistent-service control floor (control returns errSecItemNotFound/-25300,
confirming the query was valid and searching); full enumeration of 1,430 items 14.3 ms.

RELATED: #81281 describes a stale ~/.claude/.credentials.json overriding a valid
CLAUDE_CODE_OAUTH_TOKEN. This report is distinct — no env var is set here (verified
above) and the error string differs — but both indicate the client misreading its own
auth state, and #81281 independently confirms the plaintext file is authoritative.

---

Environment

| | |
|---|---|
| Claude Code | 2.1.212 (Claude Code) |
| Platform | Claude API (Max subscription) |
| OS | macOS 27.0 (build 26A5425a, developer beta) |
| Hardware | Apple M2 Pro, arm64 |
| Terminal / Shell | Ghostty 1.3.1 / zsh (also reproduced in Terminal.app) |
| Model | Opus |
| Regression | No — not tested against a prior version |

Preflight: searched existing issues; single bug report; on latest version.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗