[BUG] OAuth refresh returns 400 after early 401 before local expiresAt; concurrent sessions forced to /login
Summary
Claude Code OAuth sessions are being rejected by the server before the locally stored expiresAt time. Claude Code then attempts OAuth refresh, but POST https://platform.claude.com/v1/oauth/token returns HTTP 400. The user-facing result is repeated Please run /login / API Error: 401 Invalid authentication credentials prompts every few hours, sometimes much sooner.
This appears related to the early-revocation refresh path discussed in #52202 and potentially to the concurrent-session refresh-token race described in #24317, but this report includes Linux logs, request IDs, and the exact observed sequence from Claude Code 2.1.121.
I have intentionally omitted account email, account UUIDs, raw credential files, access tokens, and refresh tokens from this public issue. I can provide full debug logs privately to Anthropic if needed.
Environment
- Claude Code version:
2.1.121 - Install path: native installer,
~/.local/bin/claude -> ~/.local/share/claude/versions/2.1.121 - OS: Ubuntu 25.10, Linux x86_64
- Auth mode: Claude.ai OAuth / Max subscription, not API-key billing
- Shell/session manager:
zshinsidetmux - Common command wrapper:
ccis only an alias forNODE_OPTIONS="--max-old-space-size=32768" ~/.local/bin/claude --dangerously-skip-permissions- It does not call any account-switching tool or set auth env vars.
What Happened
After a successful login, Claude Code works for a while. Later, while the local OAuth credential still has a future expiresAt, API calls begin returning:
401 {"type":"error","error":{"type":"authentication_error","message":"Invalid authentication credentials"}}
Claude Code then attempts to refresh OAuth credentials:
POST https://platform.claude.com/v1/oauth/token
but the refresh request fails with:
status=400 AxiosError: Request failed with status code 400
After that, Claude Code prompts for /login again.
Precise Timeline From Latest Captured Failure
All timestamps below are UTC.
Before this failure cluster, the active credential had been refreshed at approximately:
2026-04-28T16:59:24Z
and its local OAuth expiry was approximately:
2026-04-29T00:59:24Z
So the failures below happened about 5 hours before the locally stored expiry.
Two Claude Code sessions were active in different tmux panes, sharing the same Unix user and credential store:
PID 1118880 started 2026-04-28 15:22:47Z
cwd=/data/projects/ddis/ferratomic
cmd=/home/ubuntu/.local/bin/claude --dangerously-skip-permissions --debug-file ~/.claude/debug-logs/claude-oauth-20260428T152248Z.log --resume ...
PID 1155909 started 2026-04-28 16:13:13Z
cwd=/data/projects/rr-edge
cmd=/home/ubuntu/.local/bin/claude --dangerously-skip-permissions --debug-file ~/.claude/debug-logs/claude-oauth-20260428T161314Z.log --resume ...
The first fresh 401 in the latest cluster appeared in the second session:
2026-04-28T19:58:52.417Z /v1/messages -> 401
request_id=req_011CaWm7iW1eWEpXC5FQf6HM
source=agent:custom:codex:codex-rescue
2026-04-28T19:58:52.874Z /v1/messages -> 401
request_id=req_011CaWm7kQMqRBywN9kq8pJo
source=repl_main_thread
2026-04-28T19:58:53.736Z OAuth refresh attempt:
POST https://platform.claude.com/v1/oauth/token -> 400
2026-04-28T19:58:54.136Z retry /v1/messages -> 401
request_id=req_011CaWm7qxjXcMySoRUCPjt3
source=agent:custom:codex:codex-rescue
2026-04-28T19:58:54.143Z retry /v1/messages -> 401
request_id=req_011CaWm7r5BPaU9S8CACXkrA
source=repl_main_thread
2026-04-28T19:58:55.505Z OAuth refresh attempt:
POST https://platform.claude.com/v1/oauth/token -> 400
Additional failures continued in the same session:
2026-04-28T19:58:57.786Z /v1/messages -> 401
request_id=req_011CaWm87MCDAKvnGCuGvsnz
2026-04-28T19:59:00.255Z retry /v1/messages -> 401
request_id=req_011CaWm8ESqEEduL2MqWmryS
2026-04-28T19:59:18.522Z /v1/messages -> 401
request_id=req_011CaWm9eRzyADJVd7GczwLZ
source=agent:builtin:general-purpose
2026-04-28T19:59:20.834Z retry /v1/messages -> 401
request_id=req_011CaWm9pUVE46W7t4vMYa5g
2026-04-28T19:59:23.646Z /v1/messages -> 401
request_id=req_011CaWmA2PMKwatEgYsZQrzA
source=repl_main_thread
2026-04-28T19:59:24.746Z retry /v1/messages -> 401
request_id=req_011CaWmA6HVx9BhQ7N1tRLHC
Roughly one minute later, the other active Claude Code session also failed:
2026-04-28T19:59:52.475Z /v1/messages -> 401
request_id=req_011CaWmC8zmbUxPDiPRP1xDN
source=away_summary
2026-04-28T19:59:53.349Z OAuth refresh attempt:
POST https://platform.claude.com/v1/oauth/token -> 400
2026-04-28T19:59:53.687Z retry /v1/messages -> 401
request_id=req_011CaWmCEmHvYQkXf3LquzNH
2026-04-28T19:59:55.037Z OAuth refresh attempt:
POST https://platform.claude.com/v1/oauth/token -> 400
The user reauthenticated after this, at approximately:
2026-04-28T20:00:56Z
The new credential then had:
expiresAt=2026-04-29T04:00:56Z
Earlier Captured Examples
Earlier in the same investigation, after a fresh login around 2026-04-28T15:24:04Z, the active credential had:
expiresAt=2026-04-28T23:24:04Z
but a later session failed well before that expiry:
2026-04-28T16:24:21Z /v1/messages -> 401
request_id=req_011CaWUkyMHCnP23HHubcq8W
retry request_id=req_011CaWUm3pR2mmKyeA8xPp7a
OAuth refresh attempts returned 400.
Another later failure before local expiry:
2026-04-28T17:34:04Z /v1/messages -> 401
request_id=req_011CaWa5HnPR2siADk2kfVse
retry request_id=req_011CaWa5P1Qy1yg5vHPSNXe7
OAuth refresh attempts returned 400.
Diagnostics Already Performed
The following local causes were checked and do not appear to explain the issue:
- No
ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN, orCLAUDE_CODE_OAUTH_TOKENin a clean shell. - Running Claude Code process environments did not contain those auth override env vars.
ANTHROPIC_CUSTOM_HEADERSwas not present according to debug logs.- The active auth file path is a single symlinked credential store:
~/.config/claude-code/auth.json -> ~/.claude/.credentials.json- This is OAuth auth, not API credits/API-key auth. API credits are empty and not intended to be used.
Why This Looks Like an OAuth Refresh/Rotation Problem
The local credential expiry timestamp was still in the future, but Anthropic returned 401 anyway. Claude Code appeared to correctly treat the server as authoritative and attempted refresh, which matches the behavior described by the v2.1.118 changelog and #52202.
The failing part is that the refresh grant returned HTTP 400. In the latest captured cluster, multiple concurrent Claude Code sessions and background agents were sharing one credential store. That makes a refresh-token rotation race plausible:
- Process A has an old refresh token in memory.
- Process B or a later login rotates credentials on disk.
- Process A later receives 401 and tries refreshing with its stale in-memory refresh token.
- The token endpoint returns 400.
- Other active sessions then begin failing too.
If refresh tokens are single-use and stale refresh attempts can invalidate the token family, this would explain the observed cascading /login prompts.
Expected Behavior
Claude Code should be able to run multiple concurrent sessions for the same logged-in user without causing early OAuth invalidation or repeated forced reauthentication.
At minimum, when a process receives 401 before local expiry and has multiple Claude Code processes sharing the same credential store, it should avoid using stale in-memory refresh tokens if a newer credential exists on disk. Ideally, refresh should be serialized with a lock / compare-and-swap flow, and 400 responses from the token endpoint should be logged with enough non-secret detail to distinguish invalid_grant, revoked account, stale refresh token, and other causes.
Questions for Anthropic
- Are Claude Code OAuth refresh tokens rotating and single-use?
- If an older Claude Code process attempts to use a stale refresh token, can that invalidate the current refresh token family or account session?
- Does Claude Code 2.1.121 serialize refresh attempts across processes sharing one
~/.claude/.credentials.json? - Before attempting refresh after a 401, does Claude Code re-read disk and compare whether another process has already written newer credentials?
- Can Anthropic inspect the request IDs above and identify whether the token endpoint 400 was
invalid_grant, account/session revocation, stale refresh token reuse, org/account policy, or something else?
Related Issues
- #52202 documents that OAuth handling should account for server-side early revocation before local expiry.
- #24317 appears related to concurrent Claude Code sessions and possible OAuth refresh-token races.
- #48079 and #53832 look related to persistent 401s after successful OAuth login, though the environment and exact failure pattern differ.
Additional Notes
I am concerned that repeated /login flows could be interpreted as suspicious account activity. Guidance on whether frequent re-authentication attempts are safe, and whether users should stop all existing Claude Code processes before logging in again, would be very helpful.
This issue has 14 comments on GitHub. Read the full discussion on GitHub ↗