MCP OAuth: parallel sessions sharing the credential store break refresh-token rotation (family revoked, all sessions forced to interactive re-auth)
Environment
- Claude Code 2.1.200, Windows 11
- Remote MCP server (Streamable HTTP) behind OAuth 2.1 with Dynamic Client Registration
- Authorization server rotates refresh tokens with reuse detection (observed with Supabase GoTrue v2.192.0, but this applies to any AS following OAuth 2.1 public-client guidance, where rotation is the recommended posture)
What happens
Multiple concurrent Claude Code sessions (or subagent connections) on one machine share the per-server entry in ~/.claude/.credentials.json — one OAuth session family. Each process appears to refresh from its in-memory copy of the refresh token rather than re-reading the store.
Once any process refreshes, the AS rotates the token. When another process later refreshes with its now-stale copy (outside the AS's reuse-tolerance window — 10s default on GoTrue), the AS treats it as token theft, revokes the entire family, and every session's next refresh fails. The harness then reports:
MCP server "<name>" requires re-authorization (token expired)
and only interactive /mcp re-auth recovers — mid-run agents die. Note the "(token expired)" label is misleading: the access-token TTL is fine; it's the refresh that was rejected. This cost us a root-cause hunt chasing a phantom short TTL.
Reproduction (deterministic, no timing luck)
- Authenticate a remote MCP server whose AS rotates refresh tokens (e.g., Supabase's OAuth 2.1 server).
- Simulate a sibling session: POST the stored
refreshTokenfor that server to the AS token endpoint (grant_type=refresh_token) — succeeds and rotates. Refresh once more with the returned token so the stored copy is 2 generations behind. - Wait past the AS reuse-tolerance window (>10s for GoTrue).
- At the running session's next refresh (access-token expiry), GoTrue returns
refresh_token_already_used, revokes the session family, and the session demands interactive re-auth.
We verified each step against GoTrue's source: reuse of a rotated token outside the tolerance window triggers LogoutSession on the whole family.
Expected behavior (any of these would fix it)
- (a) Re-read the credential store immediately before refreshing and use the current token, with atomic write-back after rotation
- (b) Serialize refreshes across processes (file lock / single-writer)
- (c) Per-session token families (separate DCR registration or store scope per session)
Impact
Any workflow using parallel sessions/worktrees or background agents against a rotating-AS MCP server loses auth mid-run, typically minutes after a second consumer's refresh diverges. Single-session use is unaffected (auto-refresh works correctly there — verified with a 1-hour timed run including a silent successful refresh).