MCP OAuth storage wiped mid-session (no update involved); CLI silently registers a new dynamic client, breaking auth for all remote MCP servers
Environment
- Claude Code 2.1.217 (macOS, darwin; embedded Node v26.3.0)
- Two remote HTTP MCP servers with OAuth:
https://mcp.hospitable.com/mcp(dynamic client registration) andhttps://mcp.frontapp.com/mcp(static client_id + client_secret configured viaclaude mcp add --client-id ... --client-secret) - Multiple concurrent interactive sessions (~6 different projects active in the window)
Summary
All stored MCP OAuth credentials (access tokens, refresh tokens, dynamic client registrations, AND a statically-configured client_secret) vanished from storage mid-session, with no CLI update involved — the same 2.1.217 binary was running before, during, and after. When the next session found storage empty, it silently registered a brand-new dynamic client and cached needs-auth, so the user just sees "Needs authentication" again with no indication credentials were lost. Over 8 days this produced 4 distinct dynamic client_ids registered against the same server.
We had previously attributed recurring auth loss to update-time OAuth storage wipes (observed 2026-07-01 on 2.1.197→2.1.198, and again ~2026-07-06). This incident shows storage can also be lost with no update, pointing at a concurrent read-modify-write race on the shared credential store.
Timeline (2026-07-22, UTC; all logs from ~/Library/Caches/claude-cli-nodejs/<proj>/mcp-logs-hospitable/)
- 04:08 — fresh interactive OAuth for hospitable completes: client
a251208c-adcc-4089-9e9c-0df9ff5c62ce,Token expires in: 43200,Has refresh token: true,Saving tokens. - 04:24–05:07 — sessions in 6 different projects connect successfully with that token ("Successfully connected" each time; token read fine at 05:07:13).
- 05:11:27 — a session in another project logs:
```
No token data found
No client info found
No access token in storage
Authorization URL: ...client_id=a2513799-a9b4-4a12-930a-32dda1e8a151...
a2513799...`), persisting it as the stored client info.
i.e. storage is completely empty **4 minutes after another session read it successfully**, with ~11 hours left on the access token — and the CLI silently performs a fresh dynamic client registration (
- 05:12:39 — the second OAuth server (Front, static client credentials) starts logging
Skipping connection (cached needs-auth)in a long-running session whose token refresh had succeeded at 00:21:26 ("Token refresh successful"). Its stored static client_secret is now missing (claude mcp get frontno longer shows "client_secret configured", while client_id survives in~/.claude.json). - 20:31 — still on 2.1.217 (User-Agent
claude-code/2.1.217), a new session finds "No access token in storage", "Found client info" = the self-registereda2513799, and reports needs-auth. - The 2.1.218 auto-update landed hours after the wipe — versions in User-Agent strings confirm 2.1.217 throughout the incident window.
Distinct defects
- Mid-session credential-storage loss. The entire MCP OAuth store (per-server tokens + client registrations + static client_secret) can be lost while sessions are running. The correlation with ~6 concurrent sessions suggests a last-writer-wins race: a process serializing the credentials blob from a stale in-memory copy (or treating a transient keychain read failure as "empty") clobbers everything other sessions saved.
- Silent re-registration masks the loss. When storage reads empty, the CLI immediately registers a new dynamic client and persists it. There is no warning that previously-stored credentials disappeared; the user only discovers auth is gone at next tool use. This also churns client registrations server-side — MCP providers see a stream of orphaned clients (6 from us in 8 days:
a242f8ce,a251208c,a2513799,a252fa71,a2530341,a253076f), and any refresh-token chain tied to the old client is dead. claude mcp loginclient reuse is inconsistent. With a valid registered client in storage (a2513799, self-registered at the 05:11 wipe), four consecutiveclaude mcp login hospitableattempts behaved differently: attempt 1 registered a new client (a252fa71), attempt 2 after "Authentication timeout" registered another (a2530341), attempt 3 reuseda2530341, attempt 4 registered yet another (a253076f). Whatever invalidates the stored registration compounds the orphan-client churn on the provider side.
Expected
- Concurrent sessions must not clobber each other's saved MCP OAuth credentials (per-server keys or file locking rather than whole-blob last-writer-wins).
- A keychain/storage read failure should be treated as an error (retry/surface), not as "no credentials exist".
- If stored credentials for a server disappear while a static client_id remains configured, surface a loud warning instead of silently re-registering / silently caching needs-auth.
claude mcp loginshould reuse a stored, still-valid client registration.
Impact
Every remote OAuth MCP server needs full interactive re-auth every few days; for servers with static client secrets the stored secret must be re-added from a password manager each time. We now run a SessionStart hook solely to detect these wipes.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗