[BUG] Background jobs on gateway auth intermittently fail with a synthetic "Not logged in" response
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?
When Claude Code is authenticated through a gateway (ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL) rather than OAuth or ANTHROPIC_API_KEY, background jobs intermittently return a locally generated response instead of contacting the API. The turn completes in roughly 100ms with no network round trip, and the daemon latches the job's state.json to a login-required block. The gateway token is valid the entire time, and other turns in the same session succeed; the failure is tied to how a specific worker is spawned, not to the credential itself. It appears that turns served by a claimed pre-warmed spare authenticate with no credentials, because the spare hand-off has no channel for a gateway bearer token (more detail in Additional information).
What Should Happen?
Every background turn should authenticate with the gateway token, the same way a foreground or freshly attached session on the same token does. A claimed spare should inherit or re-read the gateway credential rather than short-circuiting to a synthetic auth failure, and the daemon should not persist a false login-required block for a session whose credential is valid.
Error Messages/Logs
Synthetic response returned by the failing turn:
model: "<synthetic>"
error: "authentication_failed"
content: "Not logged in · Please run /login"
Persisted job state after the failure:
{ "state": "blocked", "needs": "login required — run /login" }
A direct request to the same gateway endpoint with the same token returns HTTP 200 throughout, and foreground/attached turns in the same session succeed, which rules out the credential itself. (Gateway hostname and token omitted intentionally.)
Steps to Reproduce
- Export
ANTHROPIC_AUTH_TOKENandANTHROPIC_BASE_URL(pointing at a gateway/proxy) with no OAuth session and noANTHROPIC_API_KEY, then start a background job. - Let the job run to an idle state, then send it further messages over time so the daemon serves the new turns by claiming pre-warmed spares from its pool.
- Some turns succeed (real model, normal latency); others return the synthetic "Not logged in" response above and the job goes to
blockedwithneeds: "login required — run /login".
Observed on one job: it ran fine for about an hour, then flapped between success and failure on later pokes. Restarting the daemon doesn't help. Reproduced again on a second incident, where three different recovery attempts (attach to the live process, stop then attach, a second stop/attach cycle) all hit the same synthetic failure. claude attach is not a reliable fix.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.206
Platform
Other
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Likely mechanism, from reading the bundled 2.1.206 CLI; treat as a lead, not a confirmed trace, given I got the intermittency explanation below wrong on the first pass. The background "spare" credential hand-off appears to only carry OAuth tokens or env-carried API keys, not a gateway bearer token. Pre-warmed spares seem to start with ANTHROPIC_AUTH_TOKEN stripped (a live claude bg-spare process confirms it's absent), and neither the claim-time env (providerEnv, empty for every job) nor the two re-auth channels I found, an auth-snapshot file and a token_update IPC message, carry anything but an OAuth accessToken. With no credential resolvable, the client fails before making a request, which gets misclassified as authentication_failed and a login prompt, and the daemon persists that in state.json.
Why it's intermittent. I previously attributed this to attach forcing a cold --session-id spawn with a real environment. That's wrong: every background launch in the daemon log, attach included, goes through the same claimed-spare mechanism, and I found no cold-spawn event at all. The one correlation I have is the claim-source tag the daemon logs: (fleet) claims failed on two separate wedged jobs, (spare)/(shell) claims didn't. Unconfirmed, and I don't know why.
Suggested fix. Re-inject the daemon's live gateway credentials into a spare at claim time; extend the OAuth-only hand-off (auth snapshot, token_update) to also cover ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL.
Workaround. claude attach <job-id> is not reliable; it just reconnects to (or re-claims) a spare that can be equally poisoned. What has worked: claude stop <job-id> (conversation is kept), then claude --resume <sessionId> in a terminal without --bg. That runs as a normal foreground process and authenticates fine, at the cost of no longer being a tracked background job. /login doesn't help; wrong auth path.
Related issues.
- #74222: same daemon symptom and
state.jsonblock, but on Claude.ai OAuth, attributed to low-memory worker retirement; this looks like the gateway-auth case of that same path. - #70355: same
ANTHROPIC_AUTH_TOKEN+ANTHROPIC_BASE_URLsetup failing with "Not logged in"; a comment notes sessions underclaude agentsfail the same way. - #65536: a
bg-spareworker starting with an incomplete environment, corroborating that claimed spares don't reliably inherit the full env.