MCP OAuth credentials repeatedly lost: discoveryState bloats keychain entry causing JSON truncation and parse failure on restart
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When an OAuth MCP server (e.g. Supabase) is connected, Claude Code stores the OAuth discoveryState (authorization server metadata) inside the same Claude Code-credentials keychain entry as the auth tokens. The Supabase discovery metadata is ~1.5 KB on its own, which pushes the total credentials JSON to ~2010 bytes where it gets truncated mid-string when written to the keychain.
On the next session start, JSON.parse() throws a SyntaxError on the malformed JSON. Claude Code resets the credential store to defaults, wiping the MCP access token. The user is prompted to re-authenticate, and the cycle repeats on every restart.
What Should Happen?
MCP OAuth access tokens should survive session restarts. The discoveryState (static, re-fetchable OAuth server metadata) should not be co-located with auth tokens in a size-limited credential store, or the store should handle larger payloads without truncation.
Error Messages/Logs
Inspect the stored credentials to see the truncation:
$ security find-generic-password -s "Claude Code-credentials" -w | xxd | tail -3
000007c0: 6573 5f73 7570 706f 7274 6564 223a 5b22 es_supported":["
000007d0: 6175 7468 6f72 697a 6174 0a authorizat.
Total size: 2010 bytes. JSON ends mid-string in the Supabase discoveryState.
`jq .` fails: "parse error: Unfinished string at EOF at line 2, column 0"
Byte layout of the truncated blob:
bytes 0–451: claudeAiOauth section (Claude Max token)
bytes 451–734: mcpOAuth header (serverName, clientId, "accessToken":"", "expiresAt":0)
bytes 734–2010: discoveryState — 1,276 bytes of OAuth server metadata, TRUNCATED
Steps to Reproduce
- Add an OAuth MCP server with a large discovery document (e.g.
plugin:supabase:supabaseathttps://mcp.supabase.com/mcp) to Claude Code - Complete the OAuth flow — MCP connects successfully
- Restart Claude Code
- Observe: MCP prompts for re-authentication immediately
- Repeat indefinitely — auth is lost on every restart
To confirm truncation is occurring:
security find-generic-password -s "Claude Code-credentials" -w | wc -c
# ~2011 bytes
security find-generic-password -s "Claude Code-credentials" -w | jq .
# parse error: Unfinished string at EOF at line 2, column 0
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.56 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Auth method: Claude Max subscription (OAuth via Claude Code CLI — no API key)
Suggested fixes (any of these would resolve it):
- Do not persist
discoveryStatein the credentials blob — re-fetch it on OAuth flow init (it's a single unauthenticated GET to a well-known endpoint and never changes) - If it must be cached, store it separately from auth tokens (e.g. a non-size-constrained file in
~/.claude/) - Handle
JSON.parsefailure on the credential blob gracefully — do not wipe existing tokens on parse error; instead attempt partial recovery or re-fetch only the missing fields
Workaround I applied to fix a secondary symptom (statusline not showing usage): added a regex fallback in ~/.claude/statusline-command.sh to extract the access token from the truncated JSON without relying on jq, since accessToken appears in the first ~200 bytes before the truncation point.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗