Stale OAuth refresh token causes persistent 401 "Invalid authentication credentials" that `/login` does not recover from (manual deletion of ~/.claude/.credentials.json required)
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
/loginshould 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.jsondoes manually). - A 401
Invalid authentication credentialsshould surface an actionable message such as "Your saved credentials are no longer valid — run/loginto re-authenticate", rather than leaving the user in the confusing "Login successful but still 401" state.
Steps to reproduce
- Log in via
/login(OAuth / Claude subscription). - Use Claude Code over a long period (weeks) so the access token is only ever refreshed, never re-granted via interactive login.
- Once the underlying
refreshTokenexpires / is rotated, requests fail with 401. - Run
/login— it reports success but requests still 401. - Delete
~/.claude/.credentials.jsonand/loginagain — 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗