Multi-session power user loses all sessions to refresh rotation races

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 1 comment · opened Aug 31, 2026

Summary

Running many concurrent long-lived Claude Code sessions (deliberate multi-session
workflow: parallel worker sessions, remote control, background agents) eventually
kills authentication for ALL sessions at once, and interactive re-login does not
stick while the other sessions are still alive. The cause appears to be OAuth
refresh-token rotation raced by multiple processes sharing
~/.claude/.credentials.json with no cross-process coordination.

Environment

  • Claude Code 2.1.251, Windows 11 Pro for Workstations (10.0.26200)
  • Subscription OAuth login (claude.ai), not API key
  • Typical steady state: 5-18 live claude processes spanning several days

(parallel sessions, remote control, orphaned process trees from closed
terminals)

Observed evidence (captured while broken)

  • ~/.claude/.credentials.json after the failure: claudeAiOauth.expiresAt

and every mcpOAuth.*.expiresAt (11 plugin connectors) all read exactly
epoch zero (1970-01-01T00:00:00Z), while
claudeAiOauth.refreshTokenExpiresAt still held a valid future date
(~2 weeks out).

  • File mtime was 3 seconds after a fresh claude process launch - a freshly

started session wrote the zeroed state.

  • 18 claude processes were alive, including a cluster of ~12 from one

session tree started 3 days earlier.

  • One long-lived session (this one) kept working throughout on its in-memory

token while the file said everything was expired.

  • Every OAuth-based MCP connector was simultaneously in the

"requires authentication" state at session start.

  • File permissions were fine (user full control, not read-only), so

persistence itself was not the blocker.

What seems to happen

  1. Each process caches tokens in memory and independently refreshes when its

own expiry timer fires.

  1. Refresh tokens rotate on use, so the first process to refresh invalidates

the refresh token every other process holds in memory.

  1. A later process attempts refresh with the now-rotated token, fails, and its

session disconnects ("randomly" from the user's perspective - it is
whichever process's timer fires second).

  1. A failing/stale process appears able to write its state back over the shared

file, clobbering the winner's fresh token - after which even a brand-new
interactive /login gets clobbered again while stale processes live.
Recovery required hunting down and killing every stale claude process
before re-login would stick.

Expected

Concurrent sessions on one machine should share one token lifecycle: either a
single-owner refresh (broker/lock so exactly one process refreshes and everyone
re-reads), or at minimum read-modify-write with file locking plus re-read-
before-refresh so a rotated token is picked up from disk instead of retried
from memory. A stale process should never be able to overwrite newer
credentials.

Impact

  • All sessions lose auth at once, including active remote-control sessions.
  • Re-login appears broken (fails to stick) until the user manually kills every

stale process - very hard to self-diagnose.

  • All plugin MCP OAuth connectors go down with it and each needs manual re-auth.

Repro sketch

  1. Log in via OAuth; start several long-lived claude sessions and leave a

detached/orphaned process tree running (close a terminal without killing
children).

  1. Wait for access-token expiry cycles to pass across multiple days.
  2. Observe cascading session auth failures, then attempt /login while stale

processes are still alive - the new token gets invalidated/overwritten.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗