claude-code - 💡(How to fix) Fix Stale OAuth refresh token causes persistent 401 "Invalid authentication credentials" that `/login` does not recover from (manual deletion of ~/.claude/.credentials.json required)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

API Error: 401 Invalid authentication credentials

Code Example

API Error: 401 Invalid authentication credentials

---

rm ~/.claude/.credentials.json
# then /login
RAW_BUFFERClick to expand / collapse

What happened

After ~2 months of using Claude Code without an interactive re-login, every request started failing with:

API Error: 401 Invalid authentication credentials

Running /login printed "Login successful", but requests kept returning the same 401. The only thing that fixed it was manually deleting the credentials file and logging in again:

rm ~/.claude/.credentials.json
# then /login

After deletion, /login performed a fresh browser OAuth grant and authentication worked normally again.

Why I think this happens

The credentials file (~/.claude/.credentials.json) stores claudeAiOauth with accessToken, refreshToken, and expiresAt. My access token had been silently refreshed for ~55 days without a full interactive login, so only expiresAt was being updated while the original grant's refreshToken aged.

When the refreshToken itself expired / was rotated server-side, the refresh attempt failed and produced the 401. Crucially, /login appears to reuse / refresh the existing token entry rather than forcing a clean new grant, so the stale refreshToken kept being used and the 401 persisted despite "Login successful". Deleting the file removed the stale state and forced a full re-authorization, which is why it recovered.

(I confirmed locally that this was not an ANTHROPIC_API_KEY conflict — no such env var was set — and not a scope/format change, since both the original and current logins were on the same 2.1.x series.)

Expected behavior

  • /login should always perform a clean grant and fully overwrite existing tokens, instead of attempting to reuse/refresh a possibly-stale entry.
  • If a token refresh fails with 401, Claude Code should automatically discard the stale credentials and fall back to a full re-authorization (i.e. do internally what rm ~/.claude/.credentials.json does manually).
  • A 401 Invalid authentication credentials should surface an actionable message such as "Your saved credentials are no longer valid — run /login to re-authenticate", rather than leaving the user in the confusing "Login successful but still 401" state.

Steps to reproduce

  1. Log in via /login (OAuth / Claude subscription).
  2. Use Claude Code over a long period (weeks) so the access token is only ever refreshed, never re-granted via interactive login.
  3. Once the underlying refreshToken expires / is rotated, requests fail with 401.
  4. Run /login — it reports success but requests still 401.
  5. Delete ~/.claude/.credentials.json and /login again — works.

Environment

  • Claude Code: 2.1.165 (native install)
  • OS: Linux (WSL2, Ubuntu)
  • Auth: OAuth (Claude Max subscription)
  • Original interactive login: ~2 months prior, on 2.1.104; refresh-only usage since.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING