[BUG] OAuth refresh returns 400 after early 401 before local expiresAt; concurrent sessions forced to /login

Open 💬 14 comments Opened Apr 28, 2026 by wvandaal

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: zsh inside tmux
  • Common command wrapper:
  • cc is only an alias for NODE_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, or CLAUDE_CODE_OAUTH_TOKEN in a clean shell.
  • Running Claude Code process environments did not contain those auth override env vars.
  • ANTHROPIC_CUSTOM_HEADERS was 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

  1. Are Claude Code OAuth refresh tokens rotating and single-use?
  2. If an older Claude Code process attempts to use a stale refresh token, can that invalidate the current refresh token family or account session?
  3. Does Claude Code 2.1.121 serialize refresh attempts across processes sharing one ~/.claude/.credentials.json?
  4. Before attempting refresh after a 401, does Claude Code re-read disk and compare whether another process has already written newer credentials?
  5. 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.

View original on GitHub ↗

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