Skip to content

fix: end a round when the provider is down instead of reviewing with it - #117

Merged
kim-em merged 2 commits into
mainfrom
provider-down-circuit-breaker
Aug 14, 2026
Merged

fix: end a round when the provider is down instead of reviewing with it#117
kim-em merged 2 commits into
mainfrom
provider-down-circuit-breaker

Conversation

@kim-em

@kim-em kim-em commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This PR ends a round when the provider is unusable, instead of filing the outage as a review.

An OAuth token was revoked mid-round on 2026-08-13. correctness had already approved; the next nine rubrics each recorded the CLI's own "Failed to authenticate. API Error: 401 OAuth access token has been revoked." as their result, five seconds apart. The round then rendered a scoreboard headed "changes requested" with nine error rows and posted it, and an errored rubric blocks the merge, so an expired credential became a merge blocker on somebody else's PR. Across the stores this had happened to 639 rubric runs and 107 whole rounds, and the commonest text was not a status code at all but "You've hit your session limit · resets 9:30pm (UTC)".

Two things were missing. error_kind read only stderr, but Claude Code in -p mode puts a total provider failure in the result text with an empty stderr, so every one of those classified as unknown_error. It now also reads a result short enough to be the whole diagnosis, under the same bound and for the same reason as TauCetiWorker's classify_agent_failure, and it knows the subscription CLIs' prose for an exhausted plan. A long review that merely quotes a status number is unaffected.

And nothing stopped the round. Two consecutive rubrics failing for a reason that will not change by calling again now abort it before anything is rendered or posted. Two rather than one, because a single 401 can be a token rotating under a long round and the retry inside run_rubric already covers that; two in a row is the credential or the plan. Only not_authenticated, quota_exhausted, and rate_limited count. Overload, timeouts, and transport failures are per-call and keep the existing behaviour.

The error case files stay as written. They are the provenance of what happened, they are what makes the next round re-run those rubrics, and TauCetiReview#105 is the standing argument that a total auth failure must stay recorded and stay loud. What changes is that none of it reaches the PR. The abort exits with a status distinct from an ordinary engine failure so the CLI reports the cause rather than a generic command failure, and its last log line names the cause in the terms a driving worker classifies on.

Replayed over all 6141 rounds in the local stores, the breaker fires on 85 and skips 424 model calls, and never fires on a round that had no errored rubric.

🤖 Prepared with Claude Code

An OAuth token was revoked mid-round on 2026-08-13. `correctness` had already
approved; the next nine rubrics each recorded the CLI's own "Failed to
authenticate. API Error: 401 OAuth access token has been revoked." as their
result, five seconds apart, and the round rendered a scoreboard with nine
blocking `error` rows and posted it to the PR. Across the stores this had
happened to 639 rubric runs and 107 whole rounds, and the commonest text was not
a status code at all but "You've hit your session limit · resets 9:30pm (UTC)".

Two gaps. error_kind read only stderr, but Claude Code in -p mode puts a total
provider failure in the result text with an empty stderr, so every one of those
classified as `unknown_error`; it now also reads a result short enough to be the
whole diagnosis, under the same bound TauCetiWorker uses, and knows the
subscription CLIs' prose for an exhausted plan. And nothing stopped the round:
two consecutive rubrics failing for a reason that will not change now abort it
before anything is rendered or posted, with a distinct exit status the CLI
reports as itself.

The error case files stay: they are the provenance, and they are what re-runs
those rubrics next round. What changes is that an outage no longer reaches a PR.
Replayed over all 6141 stored rounds the breaker fires on 85, skipping 424
model calls, and never fires on a round without an errored rubric.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1zpkPwUh41oU3mtSQGDVU
Three ways the breaker could fire on something that was not an outage, or fail
to fire on one.

Length was doing the work of a trust boundary. `text` is model output over an
untrusted diff, so a short review that never emitted its marker ("the /login
handler returns 401 on an expired token") read as an auth failure. Require a
structured failure signal first: claude's own is_error, codex's parsed error
status, or a non-zero exit. Every observed failure carries one; a model talking
about a 401 while its CLI reports success no longer does.

The streak was global while providers are chosen per rubric, so a claude auth
failure plus a codex rate limit declared the whole service down. Track it per
provider and abort only once every configured one is confirmed unavailable,
which is what the abort tells its caller.

And it counted rubrics, so a reply or contest round dispatching a single rubric
could never trip it and still rendered the error scoreboard this exists to
prevent. Count attempts: run_rubric already retries, so one rubric confirms the
diagnosis twice on its own.

Replayed over all 6155 stored rounds this still fires on 85 and skips 424 model
calls, with no round lacking an errored rubric touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1zpkPwUh41oU3mtSQGDVU
@kim-em
kim-em merged commit 2c299e1 into main Aug 14, 2026
1 check passed
@kim-em
kim-em deleted the provider-down-circuit-breaker branch August 14, 2026 02:59
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