[BUG] Credential storage: refresh token written in plaintext, and Keychain items accumulate without bound
What's Wrong?
Two related credential-storage problems on macOS, plus a scope/auth-mode issue.
- 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.
- 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).
- 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.
- 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?
- Refresh tokens stored in the Keychain, not plaintext, when the Keychain is available.
- Superseded credential items removed; item count bounded.
- Re-auth requests at least the scopes the existing credential holds, or warns explicitly.
- 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
- Authenticate normally with a Claude.ai (Max) account on macOS.
- Observe the plaintext store exists alongside the Keychain:
ls -l ~/.claude/.credentials.json
- Count credential items:
security dump-keychain | grep -c '"svce"<blob>="Claude Code-credentials'
- Use Claude Code normally for a period; re-run step 3. The count increases and
superseded items are never removed.
- Run
claude --teleport <any-session-id>. Observe the API-key message despite a
valid OAuth credential.
- Start
/loginand inspect the authorize URL:scope=user%3Ainferenceonly.
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 validCLAUDE_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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗