MCP OAuth: parallel sessions sharing the credential store break refresh-token rotation (family revoked, all sessions forced to interactive re-auth)

Open 💬 0 comments Opened Jul 4, 2026 by Lutoshi-me

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)

  1. Authenticate a remote MCP server whose AS rotates refresh tokens (e.g., Supabase's OAuth 2.1 server).
  2. Simulate a sibling session: POST the stored refreshToken for 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.
  3. Wait past the AS reuse-tolerance window (>10s for GoTrue).
  4. 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).

View original on GitHub ↗