[BUG] `CLAUDE_CODE_OAUTH_TOKEN` is shadowed by a stale `~/.claude/.credentials.json` (precedence reversed in 2.1.x) → `/login`

Open 💬 1 comment Opened Jun 13, 2026 by gigamonkey

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 from claude setup-token)
  • ~/.claude is 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 via
CLAUDE_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

  1. Mint a valid long-lived token: claude setup-token.
  2. Place a stale ~/.claude/.credentials.json (valid JSON with a

claudeAiOauth object whose accessToken is expired and whose refreshToken
has already been rotated/consumed).

  1. 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_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗