Repository navigation
Description
fix(ai): claude-code provider — an expired credentials-file token traps auth even when a valid CLAUDE_CODE_OAUTH_TOKEN is set; the env source is only reached when the file is missing or malformed
New surface from PR #1749 (commit c6acfa2a86, merged 2026-11-05): packages/ai/src/providers/claude-code-auth.ts.
The module documents a resolution order (header comment, lines 22-29):
- explicit config (
authoverride, then staticaccessToken) ~/.claude/.credentials.json(CLAUDE_CONFIG_DIRoverride)- environment
CLAUDE_CODE_OAUTH_TOKEN
and resolveCredential implements "file first, env second" — a file that is missing or malformed falls through to the env token (pinned by the test "the env token is the fallback when the file has no credential", packages/ai/test/provider/claude-code-auth.test.ts:48).
The expired case does not fall through. resolveCredential returns the expired file credential (it does not check expiry), and the expiry check lives afterwards in loadCredentialEffect (claude-code-auth.ts, the expiresAt <= now branch), which fails the whole auth with the expired message — the env source is never consulted.
Verified behavior (laptop probe, main 8185486)
Probe: ~/git/fleet/probe-2026-11-05-creator-opencode-1535/probe-claude-auth2.ts, using the module's own injectable sources, mirroring the existing test harness:
- File blob
{ accessToken: "tok-expired", expiresAt: 1000 },now: () => 2000, env{ CLAUDE_CODE_OAUTH_TOKEN: "tok-env-valid" }→ auth fails with "The Claude Code OAuth credential in ~/.claude/.credentials.json expired at … Runclaudeand complete the sign-in to renew it" — the valid env token is never tried. - Same setup with a malformed file (
"not json") → resolves to the env token. - Same setup with a live file token → file token wins (correct, pinned).
So three file states — missing, malformed, expired — behave inconsistently: two fall through to the documented second source, one traps.
Why it matters
CLAUDE_CODE_OAUTH_TOKEN is the CLI's own headless token variable — the source a headless/fleet deployment is most likely to actually maintain. On any machine where Claude Code was once signed in interactively and that login has since expired (the normal state of a headless box weeks later), setting the env token does not restore the provider: every claude-code request fails closed with instructions to run an interactive sign-in the operator may have deliberately replaced with the env token. The fail-closed design (code#6117) was about not refreshing the CLI's token and not falling back to the CLI or an API key — the env OAuth token is neither; it is the module's own documented source 2.
Decision needed / acceptance criteria
- Decide the intended precedence: (a) an expired file credential falls through to the env token (expiry = "no usable credential there", like malformed), or (b) fail-closed-on-expired is deliberate even with an env token present — in which case the header comment's resolution order and the error message must say so (the message should name that an env token is set but was not used, if that is the ruling).
- If (a):
claudeCodeAuthwith an expired file credential and a validCLAUDE_CODE_OAUTH_TOKENsucceeds with the env token as Bearer; the expired message still fires when no env token exists (existing test unchanged). - If (b): a test pins expired-file + valid-env → expired failure, and the module header documents the trap.
- Either way: a regression test in
packages/ai/test/provider/claude-code-auth.test.tscovers the expired-file + env-token combination (today only expired-file + empty-env is pinned), and the existing claude-code auth tests still pass.
Activity
developing-today commentedon Nov 5, 2026
🤖 Agent note · filed
hidden_files/issue-opencode-quota-parsers-untested-2.md; laptop probe~/git/fleet/probe-2026-11-05-creator-opencode-1535/probe-claude-auth2.ts; queue packet enqueued this run (repo-health-opencode-pilot).developing-today commentedon Nov 5, 2026
Correction to the filed agent note above: the evidence body is hidden_files/issue-opencode-claude-code-expired-blocks-env.md (the note's Links line named the sibling filing's body file in error). Probe path and all other details stand.
5 remaining items
dezren39 commentedon Nov 5, 2026
Triage (task 10614, lane opencode-a): CONFIRMED LIVE on fresh main 87c1453 (verified 2026-11-05 this shift by reading packages/ai/src/providers/claude-code-auth.ts verbatim):
resolveCredentialreturns the credentials-file credential whenever the file parses — no expiry check at resolution. The env source (CLAUDE_CODE_OAUTH_TOKEN) is read only insideresolveCredential, after the file miss/malformed branches.loadCredentialEffect, after resolution: an expired file token fails the whole auth withexpiredMessage, and env is never consulted.Decision recorded (per this issue's acceptance): fall through to env when the file credential is expired. Rationale: resolution is documented as "first credential a source can produce, in priority order", and the malformed-file fall-through is the precedent — an expired token is not a usable credential. The header's fail-closed note is about not silently degrading/refreshing; using the operator's own explicitly-set headless env token is neither, and the loud expired failure still fires when no env token exists. The fix must update the header comment to state the fall-through explicitly.
Routing: fix packet prepared at hidden_files/packets/fix-1752-packet.json (queue set to repo-health-opencode-pilot at top level and in payload; dedupe repo-health:v1:fix:developing-today/opencode:issue:1752). NOTE: enqueue attempts landed/revived as task 10689 in repo-health-code-shadow (the enqueue verb's default queue; dedupe-revive keeps returning that task to shadow even with the packet queue fields set — same failure as the #1750 packet / task 10684 this shift). Task 10689 has been cancelled with the reason recorded; the packet still needs enqueueing into repo-health-opencode-pilot by the producer/monitor.
agent-note:v1 lane=opencode-a task=10614 outcome=triaged-confirmed decision=fall-through-to-env
dezren39 commentedon Nov 5, 2026
Correction to the triage note above: the fix packet IS now enqueued in the correct queue — task 10689 (repo-health-opencode-pilot, status queued, verified by queue list). The route was REPO_QUEUE_DEFAULT_QUEUE=repo-health-opencode-pilot on the enqueue verb, which also re-stamped the revived task into that queue. Nothing is owed.
agent-note:v1 lane=opencode-a task=10614 outcome=fix-enqueued task=10689
Merge pull request #1767 from developing-today-forks/queue/10689-1752…
dezren39 commentedon Nov 5, 2026
Merged — PR #1767 landed as fbc5ada and this issue auto-closed. Verification on the PR branch (laptop):
bun test test/provider/claude-code-auth.test.ts17/17 pass; both ai tsgo configs exit 0; PR mergeable CLEAN, no Codex reviews posted.Completed by: lane opencode-a, task 10689