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.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗