[BUG] `CLAUDE_CODE_OAUTH_TOKEN` is shadowed by a stale `~/.claude/.credentials.json` (precedence reversed in 2.1.x) → `/login`
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
[This analysis was largely done by Claude Code itself but it seems right to me.]
Summary
When both CLAUDE_CODE_OAUTH_TOKEN is set in the environment and a~/.claude/.credentials.json file is present, Claude Code 2.1.x prefers the
file over the env var. If the file's credentials are stale (expired access
token whose single-use refresh token has already been consumed), Claude 401s and
drops to /login instead of falling back to the valid env-var token.
This appears to be a regression: in an earlier version (probed 2026-06-09)
the env-var token out-ranked any on-disk .credentials.json, so a stale file
could not shadow it. As of 2.1.177 (confirmed 2026-06-13) the file wins.
Environment
- Claude Code 2.1.177 (native installer build), Linux container (Ubuntu 24.04)
- Auth via
CLAUDE_CODE_OAUTH_TOKEN(a long-lived token fromclaude setup-token) ~/.claudeis bind-mounted; it contains a stale.credentials.json
Confirmation it's the file, not the token
Removing/renaming the stale ~/.claude/.credentials.json makes the same
env-var token authenticate immediately. Overlaying an empty {} at that path
also fixes it (no usable file creds → env token is used). So the env token is
valid throughout; its presence is simply not consulted while a file exists.
Impact
This breaks headless/containerized setups that inject the token viaCLAUDE_CODE_OAUTH_TOKEN while mounting a host ~/.claude. It's especially
sharp because, inside a Linux container, Claude Code itself has no Keychain and
writes a .credentials.json to that mounted directory — so a prior run
plants the very file that shadows the env token on the next run.
Suggested fix
Either restore the prior precedence (env-var token out-ranks the file), or —
more robustly — when a .credentials.json's credentials are unusable (expired +
unrefreshable), fall back to CLAUDE_CODE_OAUTH_TOKEN rather than dropping
to /login.
What Should Happen?
Expected: Claude authenticates using the valid env-var token when present.
Error Messages/Logs
Steps to Reproduce
- Mint a valid long-lived token:
claude setup-token. - Place a stale
~/.claude/.credentials.json(valid JSON with a
claudeAiOauth object whose accessToken is expired and whose refreshToken
has already been rotated/consumed).
- Launch with the valid token in the environment:
``bash``
CLAUDE_CODE_OAUTH_TOKEN=<valid-108-char-token> claude --dangerously-skip-permissions
Expected: Claude authenticates using the valid env-var token.
Actual: Claude uses the stale file, fails to refresh (401 on the dead refresh
token), and prompts /login — even though a valid token was supplied via the
environment.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.177
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗