Headless CLI sends empty then wrong Authorization bearer instead of provided OAuth credentials (401 Invalid bearer token), machine-specific

Open 💬 0 comments Opened Jul 13, 2026 by mattcbh

Summary

On one of my two Macs, headless claude -p cannot authenticate with valid Max-subscription OAuth credentials. The CLI does not send the credential it was given. Captured on the wire: the first request goes out with an empty bearer (Authorization: Bearer with nothing after it), and retries carry a different 108-character credential that is not the one provided and does not exist anywhere on disk. The API rejects it with 401 Invalid bearer token. The identical CLI version on my other Mac sends the provided token correctly and works.

Environment

  • Failing machine: Mac mini (arm64), macOS Darwin 25.x, headless (SSH only). Native install.
  • Reproduced on CLI versions 2.1.119, 2.1.206, and 2.1.207 (native installer builds).
  • Working machine: MacBook (arm64), CLI 2.1.206, same account, same token.
  • Auth under test: Max 20x subscription OAuth, tried both ways:
  • CLAUDE_CODE_OAUTH_TOKEN env var from claude setup-token
  • .credentials.json written by a successful interactive /login (subscriptionType max, correct rate limit tier)

Evidence

  1. The OAuth token itself is valid from the failing machine: a raw curl https://api.anthropic.com/v1/messages -H "Authorization: Bearer <token>" returns HTTP 200 from the same box, with or without the oauth beta header.
  2. Wire capture (pointed ANTHROPIC_BASE_URL at a local HTTP sink and logged headers): first POST /v1/messages?beta=true arrives with Authorization: value of exactly 6 characters, i.e. the literal string Bearer with an empty token. Subsequent attempts carry a 115-character value ("Bearer " + 108 chars) whose hash does not match "Bearer " + the provided token. The same ghost value is sent on every run, across fresh CLAUDE_CONFIG_DIRs and even a throwaway HOME. An x-api-key header is also present on these requests alongside Authorization.
  3. The working machine's capture shows the correct "Bearer " + provided token on the first request.

Ruled out on the failing machine

  • Keychain: the Claude Code-credentials generic password was deleted; behavior unchanged; the entry stays absent.
  • Stored state: brute-force hashed every token-shaped string in ~/.claude.json, config dirs, statsig, caches; the ghost credential matches none of them.
  • Env: no ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, proxy vars, or launchd setenv values present.
  • Managed settings: none. apiKeyHelper: removed from the equation via clean CLAUDE_CONFIG_DIR and via throwaway HOME with only .credentials.json + minimal settings; still 401.
  • Clock skew: zero versus the API's Date header.

Odd data point

Immediately after creating a clean profile, one invocation on 2.1.119 authenticated successfully with the env token. Every attempt after that, on all versions, fails as described. This made it look intermittent at first; it has been 100 percent reproducible since.

Impact

Headless automation on this machine cannot use subscription auth at all; it only works with API-key billing. Happy to run diagnostic builds or provide the capture tooling.

View original on GitHub ↗