OAuth token refresh race condition with multiple concurrent CLI processes

Resolved 💬 3 comments Opened Feb 23, 2026 by kvooak Closed Feb 27, 2026

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

  1. Open 2+ terminal tabs, each running claude (or have background agents spawned across sessions)
  2. Wait for the OAuth access token to expire (triggers a refresh)
  3. Multiple processes read the same refresh_token from ~/.claude/.credentials.json
  4. First process redeems it successfully, writes a new refresh token to disk
  5. 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:

  1. File lock (e.g., proper-lockfile or OS-level flock) around the read-refresh-write cycle on .credentials.json
  2. Retry with re-read: On 404, re-read .credentials.json from disk (another process may have updated it) and use the new access token instead of refreshing again
  3. 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 claude CLI sessions simultaneously
  • Run claude logout && claude login when it occurs

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗