fix: AI reviewer authenticated via low-balance API key instead of subscription#98
Merged
Merged
Conversation
…scription Both PR #92's and PR #96's review runs failed with "Credit balance is too low" (the exit-causing error; an unrelated "workspace has not been trusted" permissions warning prints alongside it and had been masking the real cause). Root cause, confirmed live: reviewer-adapter.ts runs via noSandbox() + sandcastle's top-level run(), which inherits the devcontainer's own process env — ANTHROPIC_API_KEY (ADR-0018 cockpit passthrough; also needed by headroom's own compress() calls, ADR-0023) sits alongside CLAUDE_CODE_OAUTH_TOKEN (resolved separately from .sandcastle/.env by sandcastle for agent auth). Claude Code prefers an explicit API key over the subscription token whenever both are present, so every review silently authenticated against the API key — and failed outright once that key's balance ran out. Verified precisely: `ANTHROPIC_API_KEY="" CLAUDE_CODE_OAUTH_TOKEN=<real> claude --print ...` succeeds where the unmodified env fails. Confirmed this specific fix mechanism (claudeCode(model, { env }) on sandcastle's top-level run()) actually threads through — unlike SandboxRunner's createSandbox() + .run() path (see PR #97), where AgentProvider.env is silently discarded. Fix: force ANTHROPIC_API_KEY to an empty string (not omitted — Claude Code only falls back to OAuth when the var is unset/empty) on all 4 claudeCode() calls in reviewer-adapter.ts, via a shared FORCE_OAUTH_ENV constant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Investigating why PR #92's and PR #96's AI reviews both failed with
changes-requestedfail-safe verdicts turned up a real, previously-misdiagnosed root cause.Both review logs show the same fatal line:
The "workspace not trusted" line is a harmless warning that always prints — it's not what killed the process. "Credit balance is too low" is the actual failure.
Root cause:
reviewer-adapter.tsruns vianoSandbox()+ sandcastle's top-levelrun(), which inherits the devcontainer's own process env. That env carriesANTHROPIC_API_KEY(ADR-0018 cockpit passthrough; also needed by headroom's owncompress()calls, ADR-0023) alongsideCLAUDE_CODE_OAUTH_TOKEN(resolved separately from.sandcastle/.envby sandcastle for agent auth). Claude Code prefers an explicit API key over the subscription token whenever both are present — so every review silently authenticated against the API key, and failed outright once that key's balance ran out.Verified precisely:
ANTHROPIC_API_KEY="" CLAUDE_CODE_OAUTH_TOKEN=<real> claude --print ...succeeds where the unmodified env fails. Also confirmed the fix mechanism itself works on this specific code path (sandcastle's top-levelrun()does correctly mergeAgentProvider.env— unlikeSandboxRunner'screateSandbox()+.run()path fixed in #97, where it's silently discarded).Fix: force
ANTHROPIC_API_KEYto an empty string (not omitted — Claude Code only falls back to OAuth when the var is unset/empty) on all 4claudeCode()calls inreviewer-adapter.ts, via a sharedFORCE_OAUTH_ENVconstant.Test plan
npm test— 206/206 passingtsc --noEmit— cleanrun()API (same onereviewer-adapter.tsuses): aclaudeCode(model, { env: { ANTHROPIC_API_KEY: "" } })call against a realnoSandbox()invocation succeeded using the subscription token