Skip to content

fix(queue): the verdict-stability backoff misses the webhook path, eats a manual retrigger, and ignores force #10222

Description

@JSONbored

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.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions