[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.jsonmcpOAuth 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 -p from launchd)

Observed

  • .credentials.json is 906 KB with 1,681 mcpOAuth records 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 distinct clientId, but exactly 1 distinct accessToken and 1 distinct refreshToken (compared by hash). It is the same token stored 113 times.
  • The serverUrl is 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 the ccr-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

  1. Log in via claude.ai with several connectors enabled.
  2. Note python3 -c "import json;print(len(json.load(open('$HOME/.claude/.credentials.json'))['mcpOAuth']))".
  3. Start and end a new claude session (interactive or claude -p "hi").
  4. Re-run step 2: the count increases by the number of enabled connectors; serverUrl of 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.

View original on GitHub ↗