[BUG] .credentials.json mcpOAuth grows unbounded: claude.ai connector tokens re-stored under a session-scoped serverUrl every session (1,681 records / 906 KB, same token ×113)
Status Open
Reported on v2.1.265
Maintainer reply None cached
Activity 0 comments · opened Sep 10, 2026
Summary
~/.claude/.credentials.json → mcpOAuth grows without bound. Every new session re-stores the OAuth record for each claude.ai connector under a new key and a new serverUrl, even though the access/refresh token is identical to the previous record. Nothing is ever pruned.
Environment
- Claude Code 2.1.265 (native install,
~/.local/share/claude/versions/2.1.265) - macOS 26 (Darwin 25.3.0), Apple Silicon
- Auth: claude.ai login (no API key); ~29 connectors enabled on the account
- Heavy multi-session use (interactive sessions, subagents, headless
claude -pfrom launchd)
Observed
.credentials.jsonis 906 KB with 1,681mcpOAuthrecords across 29 distinct connectors.- The claude.ai connectors each have ~113 records (Gmail 113, Slack 113, Zoom 113, QuickBooks 113, Netlify 113, Google Drive 113, Google Calendar 113, Plaud 113, Tally 113, Claude_Code_Remote 112, Notion 111, Microsoft_365 111, …). The count matches the number of sessions started since each connector was added.
- For the 113 Gmail records: 113 distinct
serverUrl, 113 distinctclientId, but exactly 1 distinctaccessTokenand 1 distinctrefreshToken(compared by hash). It is the same token stored 113 times. - The
serverUrlis session-scoped:https://api.anthropic.com/v2/ccr-sessions/<session-id>/mcp?mcp_server_id=…&mcp_url=…&toolbox_mcp_server_id=…. Records differ only in theccr-sessions/<id>path segment and the query params. The record key is<ServerName>|<16-hex hash>, presumably derived from that URL, so every session produces a fresh key. - Only 2 of 1,681 records carry
expiresAt; the rest have no expiry metadata that would allow cleanup. - Growth observed over one day: 1,653 → 1,681 records (28 new = 2 sessions × 14 connectors).
Expected
- One record per connector (key by connector /
mcp_server_id, not by the session-scoped URL), updated in place on refresh. - Or: prune records whose session has ended, and dedupe identical tokens on write.
Impact
- File parsed on every session start keeps growing (already ~1 MB; ~14 records added per session).
- The same refresh token is duplicated ~113× on disk, which widens the blast radius of any credential-file exposure and makes manual cleanup risky (unclear which record a live session reads).
- Users cannot safely hand-edit the file without risking a re-login on every connector.
Repro
- Log in via claude.ai with several connectors enabled.
- Note
python3 -c "import json;print(len(json.load(open('$HOME/.claude/.credentials.json'))['mcpOAuth']))". - Start and end a new
claudesession (interactive orclaude -p "hi"). - Re-run step 2: the count increases by the number of enabled connectors;
serverUrlof the new records contains the new session id; token values match the previous records.
No secrets included above; token comparison was done by hash locally.