MCP OAuth writes corrupt shared credential store, causing Team plan logout across all sessions
Summary
Authenticating an HTTP MCP server (e.g. Linear at https://mcp.linear.app/mcp) causes Claude Code to lose its Team plan authentication across all active sessions. All sessions display "you've been logged out" and CC falls back to API billing.
Root Cause
Claude Code stores both its own subscription OAuth (claudeAiOauth) and all MCP server OAuth tokens (mcpOAuth) in a single macOS Keychain entry (Claude Code-credentials, one JSON blob). When an MCP OAuth write occurs with a stale cache, null Keychain read, or concurrent read-modify-write race, the resulting JSON is written back without valid claudeAiOauth — logging out all sessions.
Environment
- CC version: 2.1.96 (macOS ARM64)
- Plan: Team (Stripe subscription)
- Concurrent sessions: 5 interactive + 1 headless + MCP companions = 36-47 processes sharing one Keychain entry
- MCP servers: Linear (HTTP/OAuth), Vercel (HTTP/OAuth, disabled), 7 stdio servers
Reproduction
- Have 2+ CC sessions running with Team plan auth
- Run
/mcpand authenticate Linear MCP (https://mcp.linear.app/mcp) - After OAuth callback completes, all sessions show "logged out"
claude auth statusreportsloggedIn: false- Keychain entry either missing entirely, or contains only
mcpOAuth(noclaudeAiOauth)
Repro protocol (before/after measurement)
# Before:
security find-generic-password -a "$USER" -s "Claude Code-credentials" -w | python3 -c "import json,sys; d=json.load(sys.stdin); print(list(d.keys()), len(json.dumps(d)), 'bytes')"
# Output: ['claudeAiOauth', 'mcpOAuth'] 1261 bytes
# Trigger Linear MCP auth via /mcp...
# After:
# Expected smoking gun: mcpOAuth present, claudeAiOauth missing
Contributing Bugs
This is not a single bug but at least 5 interacting issues:
| # | Issue | Mechanism |
|---|-------|-----------|
| 1 | #24317 | Concurrent session race condition. proper-lockfile with 7.5s max lock budget is insufficient for 10+ processes. Single-use refresh token: one session refreshing invalidates all others. |
| 2 | #33995 | Token refresh never persists to disk. refreshOAuthToken() succeeds in memory but update() is never called to write back. |
| 3 | #36779 | MCP OAuth write drops claudeAiOauth. Only mcpOAuth persisted, auth token missing. Closest exact-match to this symptom. |
| 4 | #28542 | JSON truncation. Discovery metadata bloats shared Keychain JSON past ~2010 bytes → truncation → JSON.parse() fails → full credential reset. |
| 5 | #37512 | CLAUDE_CODE_OAUTH_TOKEN silently deletes Keychain. Write to plaintext backend triggers delete from Keychain backend. |
Additional Related Issues
- #5706 — MCP refresh tokens stored but never used (OPEN since 2025, 29 comments)
- #43000 — Multi-terminal MCP OAuth: storage key includes callback port, causing duplicate entries
- #44830 — RFC 9728 discovery poisoning: one transient failure permanently blocks OAuth
- #40834 — Switching auth modes invalidates ALL HTTP MCP OAuth tokens
- #38074 — Background
ccr_inferencesessions disrupt CLI sessions every ~10 min - #42603 — Plugin hooks can read/refresh CC credentials with zero permission checks
- #1757 — Users constantly required to re-login (130+ comments, oldest auth issue)
Proposed Fix
The fundamental issue is the shared credential store. claudeAiOauth and mcpOAuth should not be in the same Keychain entry. Either:
- Separate Keychain entries for CC auth vs MCP auth (eliminates the clobber entirely)
- Atomic read-modify-write with proper locking (the current
proper-lockfilebudget is too low for typical multi-session environments) - Per-session MCP OAuth state that doesn't touch the shared credential store
Local Workaround
I built a credential backup/restore system (cc-auth-guard.sh) that:
- Snapshots the Keychain JSON at each SessionStart
- Detects when
claudeAiOauthdisappears after MCP auth - Auto-restores from the most recent good backup (merging current
mcpOAuthwith backed-upclaudeAiOauth)
This mitigates the symptom but doesn't fix the race condition.
4 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
This is intentionally not a duplicate of any single issue — it is a consolidated root-cause analysis showing that #36779, #24317, and #28542 (plus #33995, #37512, #42603, #38074) are interconnected bugs in the same shared credential store, not independent issues.
The key finding that no individual issue captures:
CC stores
claudeAiOauth(subscription auth) andmcpOAuth(all MCP server tokens) in a single Keychain entry. This shared mutable state means:None of these issues individually explains the full symptom chain (authenticate MCP → all sessions logged out → falls back to API billing). Each was filed in isolation. This issue documents the interaction between them and provides a repro protocol + proposed architectural fix (separate credential stores).
Workaround deployed: Switched from HTTP/OAuth transport to
mcp-stdio(stdio relay that handles OAuth independently, keyed by server URL not callback port). Keychain JSON reduced from 1,261 → 503 bytes.mcpOAuthnow empty — MCP auth no longer touches the shared credential store at all.This issue was fixed as of version 2.1.136.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.