MCP OAuth writes corrupt shared credential store, causing Team plan logout across all sessions

Status Fixed / completed
Reported on v2.1.96
Maintainer reply ✓ Yes — claude[bot]
Activity 4 comments · opened Apr 9, 2026 · closed May 9, 2026
💡 Likely answer: A maintainer (claude[bot], contributor) responded on this thread — see the highlighted reply below.

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

  1. Have 2+ CC sessions running with Team plan auth
  2. Run /mcp and authenticate Linear MCP (https://mcp.linear.app/mcp)
  3. After OAuth callback completes, all sessions show "logged out"
  4. claude auth status reports loggedIn: false
  5. Keychain entry either missing entirely, or contains only mcpOAuth (no claudeAiOauth)

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_inference sessions 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:

  1. Separate Keychain entries for CC auth vs MCP auth (eliminates the clobber entirely)
  2. Atomic read-modify-write with proper locking (the current proper-lockfile budget is too low for typical multi-session environments)
  3. 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 claudeAiOauth disappears after MCP auth
  • Auto-restores from the most recent good backup (merging current mcpOAuth with backed-up claudeAiOauth)

This mitigates the symptom but doesn't fix the race condition.

View original on GitHub ↗

4 Comments

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/36779
  2. https://github.com/anthropics/claude-code/issues/24317
  3. https://github.com/anthropics/claude-code/issues/28542

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ghost · 6 months ago

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) and mcpOAuth (all MCP server tokens) in a single Keychain entry. This shared mutable state means:

  • #24317's race condition → can corrupt the shared blob
  • #33995's write-back failure → leaves stale tokens that trigger #24317's refresh race
  • #28542's JSON truncation → destroys both auth types simultaneously
  • #36779's "only mcpOAuth stored" → is the SYMPTOM when any of the above corrupts the shared blob
  • #37512's deletion bug → wipes the entire entry including mcpOAuth
  • #38074's background rotation → compounds the race across even more processes

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. mcpOAuth now empty — MCP auth no longer touches the shared credential store at all.

claude[bot] contributor · 5 months ago

This issue was fixed as of version 2.1.136.

github-actions[bot] · 3 months ago

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.