Three gaps in the verdict-stability backoff shipped by #10204 (issue #10184). All three are live on main.
1. The webhook path is not guarded at all
There are two publish-and-maintain sites in src/queue/processors.ts:
reReviewStoredPullRequest — the sweep / CI-completion path. Guarded.
handlePullRequestWebhookEvent — the pull_request webhook path. Not guarded.
grep -n "shouldSkipStableVerdict" src/queue/processors.ts returns exactly one hit.
This matters because the webhook path is the one carrying the churn. Decision records on the Orb over 24h:
reevaluation_reason |
count |
upstream_state_change |
293 |
| (first evaluation, no reason) |
265 |
pipeline_error |
51 |
upstream_state_change is deriveReevaluationReason's mapping for a raw GitHub delivery id — a real webhook event. That is the dominant source of repeat evaluations, and it is the path with no backoff. #10184's own evidence names "CI completions, label writes, sibling activity", and a label write is a pull_request.labeled delivery — often one the engine itself just caused.
2. A manual "Re-run LoopOver review" click is consumed, then discarded
In reReviewStoredPullRequest the ordering is:
| line |
|
| 4374 |
options.onReachedReadiness?.() |
| 4380 |
consumePendingPrPanelRetrigger(...) — one-shot, clears the marker |
| 4463 |
the backoff guard → return false |
So a user's retrigger marker is consumed at 4380 and the pass then backs off at 4463 and returns without reviewing. The marker is gone, nothing re-triggers, and the click is silently lost — the exact failure #7626 exists to prevent.
onReachedReadiness firing at 4374 has the same shape: regatePullRequest charges its bounded repair-attempt budget for a pass that never reviewed.
3. options.force is not consulted
reReviewStoredPullRequest takes options.force and the manual re-gate path passes force: true. The guard never reads it, so an operator's explicit re-gate can be silently backed off. previewPollAttempt has the same problem — a visual-preview poll is an explicit request for another pass, and suppressing it breaks the poll chain.
Fix
Move the guard to before the readiness gate — ahead of onReachedReadiness, the retrigger consumption, and the lock claim (so no lock needs taking or releasing), and bypass it entirely when the caller explicitly asked for this pass (options.force, previewPollAttempt). Then add the same guard to the webhook path, placed so it short-circuits before that path's own retrigger consumption.
Backoff must never suppress a pass a human explicitly asked for. Everything else about #10204 — the fingerprint, the cap, the head-SHA keying, failing open on missing state — is correct and unchanged.
Three gaps in the verdict-stability backoff shipped by #10204 (issue #10184). All three are live on
main.1. The webhook path is not guarded at all
There are two publish-and-maintain sites in
src/queue/processors.ts:reReviewStoredPullRequest— the sweep / CI-completion path. Guarded.handlePullRequestWebhookEvent— thepull_requestwebhook path. Not guarded.grep -n "shouldSkipStableVerdict" src/queue/processors.tsreturns exactly one hit.This matters because the webhook path is the one carrying the churn. Decision records on the Orb over 24h:
reevaluation_reasonupstream_state_changepipeline_errorupstream_state_changeisderiveReevaluationReason's mapping for a raw GitHub delivery id — a real webhook event. That is the dominant source of repeat evaluations, and it is the path with no backoff. #10184's own evidence names "CI completions, label writes, sibling activity", and a label write is apull_request.labeleddelivery — often one the engine itself just caused.2. A manual "Re-run LoopOver review" click is consumed, then discarded
In
reReviewStoredPullRequestthe ordering is:options.onReachedReadiness?.()consumePendingPrPanelRetrigger(...)— one-shot, clears the markerreturn falseSo a user's retrigger marker is consumed at 4380 and the pass then backs off at 4463 and returns without reviewing. The marker is gone, nothing re-triggers, and the click is silently lost — the exact failure #7626 exists to prevent.
onReachedReadinessfiring at 4374 has the same shape:regatePullRequestcharges its bounded repair-attempt budget for a pass that never reviewed.3.
options.forceis not consultedreReviewStoredPullRequesttakesoptions.forceand the manual re-gate path passesforce: true. The guard never reads it, so an operator's explicit re-gate can be silently backed off.previewPollAttempthas the same problem — a visual-preview poll is an explicit request for another pass, and suppressing it breaks the poll chain.Fix
Move the guard to before the readiness gate — ahead of
onReachedReadiness, the retrigger consumption, and the lock claim (so no lock needs taking or releasing), and bypass it entirely when the caller explicitly asked for this pass (options.force,previewPollAttempt). Then add the same guard to the webhook path, placed so it short-circuits before that path's own retrigger consumption.Backoff must never suppress a pass a human explicitly asked for. Everything else about #10204 — the fingerprint, the cap, the head-SHA keying, failing open on missing state — is correct and unchanged.