Skip to content

fix: AI reviewer authenticated via low-balance API key instead of subscription#98

Merged
lsfera merged 1 commit into
mainfrom
fix/reviewer-auth-prefers-api-key
Jul 3, 2026
Merged

fix: AI reviewer authenticated via low-balance API key instead of subscription#98
lsfera merged 1 commit into
mainfrom
fix/reviewer-auth-prefers-api-key

Conversation

@lsfera

@lsfera lsfera commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Investigating why PR #92's and PR #96's AI reviews both failed with changes-requested fail-safe verdicts turned up a real, previously-misdiagnosed root cause.

Both review logs show the same fatal line:

Credit balance is too low
Agent invocation failed: claude-code exited with code 1:
Ignoring 64 permissions.allow entries from .claude/settings.local.json: this workspace has not been trusted...

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.ts runs via noSandbox() + sandcastle's top-level run(), which inherits the devcontainer's own process env. That env carries ANTHROPIC_API_KEY (ADR-0018 cockpit passthrough; also needed by headroom's own compress() calls, ADR-0023) 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. Also confirmed the fix mechanism itself works on this specific code path (sandcastle's top-level run() does correctly merge AgentProvider.env — unlike SandboxRunner's createSandbox() + .run() path fixed in #97, where it's 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.

Test plan

  • npm test — 206/206 passing
  • tsc --noEmit — clean
  • Live-verified via sandcastle's actual run() API (same one reviewer-adapter.ts uses): a claudeCode(model, { env: { ANTHROPIC_API_KEY: "" } }) call against a real noSandbox() invocation succeeded using the subscription token

…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.
@lsfera lsfera merged commit 3d7034a into main Jul 3, 2026
3 checks passed
@lsfera lsfera deleted the fix/reviewer-auth-prefers-api-key branch July 3, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant