OAuth token refresh race condition with multiple concurrent CLI processes
Bug Description
When multiple claude CLI processes run concurrently, they race on refreshing the single-use OAuth refresh token stored in ~/.claude/.credentials.json. The loser of the race gets a 404 and loses authentication with no automatic recovery.
Environment
- OS: Windows 11 Pro 10.0.26200
- Shell: Git Bash
- Claude Code version: Latest (claude-code CLI)
Steps to Reproduce
- Open 2+ terminal tabs, each running
claude(or have background agents spawned across sessions) - Wait for the OAuth access token to expire (triggers a refresh)
- Multiple processes read the same
refresh_tokenfrom~/.claude/.credentials.json - First process redeems it successfully, writes a new refresh token to disk
- Second process sends the now-consumed refresh token → HTTP 404
not_found_error
Observed Behavior
{"level":40,"service":"token-manager","msg":"Refresh token is invalid or expired. OAuth refresh tokens are single-use. Please re-authenticate: run \"claude logout && claude login\" in terminal."}
{"level":50,"service":"token-manager","error":"HTTP 404: {\"type\":\"error\",\"error\":{\"type\":\"not_found_error\",\"message\":\"Not Found\"}}","msg":"Token refresh failed"}
Confirmed 3 concurrent claude.exe processes at the time of failure via tasklist.
Root Cause
OAuth refresh tokens are single-use. The refresh flow in the token manager does a read → HTTP refresh → write cycle on .credentials.json without any file locking or coordination. When two processes read the same refresh token, the first one consumes it server-side, and the second gets a 404 because the token no longer exists.
Time Process A Process B
───── ───────────────────────────── ─────────────────────────────
t1 reads refresh_token=T1 reads refresh_token=T1
t2 sends T1 → gets T2, writes T2
t3 sends T1 → 404 (T1 consumed)
Suggested Fix
Add mutual exclusion around the token refresh path. Options:
- File lock (e.g.,
proper-lockfileor OS-levelflock) around the read-refresh-write cycle on.credentials.json - Retry with re-read: On 404, re-read
.credentials.jsonfrom disk (another process may have updated it) and use the new access token instead of refreshing again - Both: Lock for the happy path, retry-with-re-read as a fallback for robustness
Option 2 is the simplest and most resilient — if process A gets a 404, it just re-reads the file (which process B already updated with a fresh token) and continues without re-authenticating.
Current Workaround
- Avoid running multiple
claudeCLI sessions simultaneously - Run
claude logout && claude loginwhen it occurs
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗