[BUG] Background jobs on gateway auth intermittently fail with a synthetic "Not logged in" response

Open 💬 0 comments Opened Jul 10, 2026 by hyperspacemark

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

  1. Export ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL (pointing at a gateway/proxy) with no OAuth session and no ANTHROPIC_API_KEY, then start a background job.
  2. 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.
  3. Some turns succeed (real model, normal latency); others return the synthetic "Not logged in" response above and the job goes to blocked with needs: "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.json block, 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_URL setup failing with "Not logged in"; a comment notes sessions under claude agents fail the same way.
  • #65536: a bg-spare worker starting with an incomplete environment, corroborating that claimed spares don't reliably inherit the full env.

View original on GitHub ↗