fix(gate): lift the bot's own manual-review hold, and stop drafts wedging the merge train - #9942
Conversation
…ging the merge train Two independent reasons an autonomous queue stalls with no human in it, both found on live PRs. 1) The manual-review label was a ONE-WAY LATCH. The sibling-label cleanup deliberately refuses to touch it, because the same label string is also a maintainer's manual freeze and the planner had no way to tell them apart -- and silently undoing a human's freeze is far worse than leaving a stale hold. That caution was right; the missing piece was provenance. Seen on #9935: the label went on when a finding fired against a stale head, a rebase removed the cause, a fresh escalated review returned ZERO findings, and the PR was mergeable, green and clean -- and still would not merge: agent.action.merge | denied | manual-review label "manual-review" is present on the live PR -- merge not executed It took a human removing the label by hand, in the mode that is supposed to have no human in it. The planner now records the head SHA and reason when IT applies the label, and may lift it once manualHoldReason is null (no reason wants a hold this pass, so a label applied for reason A can never be lifted by reason B clearing). A label with NO provenance -- a human's, or one predating this -- is never touched. That asymmetry is the whole safety property. 2) A draft sibling wedged the merge train. The train already evicts conflicted and manual-review-held siblings on the principle that waiting for a PR which is not trying to reach merge buys nothing the 24h cap does not already bound far worse. A DRAFT is the strongest form of that signal: GitHub itself refuses to merge one, so it is not one merge away from landing, it is one author action away from being eligible at all. The production shape: a maintainer PR with red CI cannot be auto-closed (maintainers are exempt on purpose, so they can iterate), so it stays open and overlapping for as long as the fix takes -- holding every newer overlapping PR behind it, green and approved, for up to the full 24 hours. Marking it draft is the author saying "skip me", and the train now understands that. Eviction removes ONE PR from the queue; it does not disable the gate. A real older sibling behind the draft still holds the line.
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-30 19:20:52 UTC
Review summary Nits — 6 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9942 +/- ##
=======================================
Coverage 91.87% 91.87%
=======================================
Files 923 923
Lines 113461 113474 +13
Branches 27343 27348 +5
=======================================
+ Hits 104244 104257 +13
Misses 7928 7928
Partials 1289 1289
Flags with carried forward coverage won't be shown. Click here to find out more.
|
#9942 added the release guarded on `manualHoldReason === null`, and stated that no add site could be live at that point. That ternary covers only the guardrail hit, unverified CI, an action_required conclusion and a not-review-good verdict. Every would-MERGE hold -- migration collision, unlinked-issue match, priority eligibility, unlinked-issue close, unstable merge state -- fires on `reviewGood` with a SUCCESS conclusion, which is precisely when manualHoldReason is null. So the release stripped the label while the hold was still standing, and that label is exactly what the executor checks to deny merge and approve: removing it removes the enforcement for a hold nothing has resolved. Verified against the merged planner before changing it -- all five holds planned a removal. The condition is now one named predicate listing every reason that would ADD the label, so a hold added later cannot silently fail to suppress the release. The counterweight is tested too: with nothing holding, the label is still released, so the one-way latch #9942 fixed does not come back.
…ive (#9943) #9942 added the release guarded on `manualHoldReason === null`, and stated that no add site could be live at that point. That ternary covers only the guardrail hit, unverified CI, an action_required conclusion and a not-review-good verdict. Every would-MERGE hold -- migration collision, unlinked-issue match, priority eligibility, unlinked-issue close, unstable merge state -- fires on `reviewGood` with a SUCCESS conclusion, which is precisely when manualHoldReason is null. So the release stripped the label while the hold was still standing, and that label is exactly what the executor checks to deny merge and approve: removing it removes the enforcement for a hold nothing has resolved. Verified against the merged planner before changing it -- all five holds planned a removal. The condition is now one named predicate listing every reason that would ADD the label, so a hold added later cannot silently fail to suppress the release. The counterweight is tested too: with nothing holding, the label is still released, so the one-way latch #9942 fixed does not come back.
Closes #9939
Two independent reasons an autonomous queue stalls with no human in it. Both found on live PRs.
1. The manual-review label was a one-way latch
Seen on #9935: the label went on when a finding fired against a stale head, a rebase removed the cause, a fresh escalated review returned zero findings, and the PR was
MERGEABLE/CLEAN/ fully green. It still would not merge:It took a human removing the label by hand — in the mode that is supposed to have no human in it.
The sibling-label cleanup deliberately refuses to touch this label, because the same string is also a maintainer’s manual freeze and the planner had no way to tell them apart. That caution is correct and stays: silently undoing a human’s deliberate hold is far worse than leaving a stale one. The missing piece was never the caution — it was the provenance.
The planner now records the head SHA and reason when it applies the label, and may lift it once
manualHoldReason === null— i.e. no reason wants a hold this pass, which is what makes it impossible to lift a label applied for reason A because reason B cleared. A label with no provenance is never touched. That asymmetry is the entire safety property, and it is asserted directly.Deliberately not scoped to the recorded head on removal: a rebase resolving the cause is the single most common way a hold goes stale, and refusing to lift it there would leave the exact #9935 case unfixed.
2. A draft sibling wedged the merge train
The train already evicts conflicted and manual-review-held siblings, on the principle (#9039) that waiting for a PR not trying to reach merge buys nothing the 24h cap does not already bound far worse.
A draft is the strongest form of that signal — GitHub itself refuses to merge one, so it is not one merge away from landing, it is one author action away from being eligible at all.
The production shape you hit: a maintainer PR with red CI cannot be auto-closed (maintainers are exempt on purpose, so they can iterate), so it stays open and overlapping for as long as the fix takes — holding every newer overlapping PR behind it, green and approved, for up to the full 24 hours. Marking it draft is the author saying "skip me", and the train now understands that.
Eviction removes one PR from the queue; it does not disable the gate. A test pins that a real older sibling behind the draft still holds the line.
Also evicts on GitHub’s own
mergeableState === "draft", for any caller that only resolved that field.Tests
Provenance: lifts a bot-applied hold; never touches a label with no provenance; does not lift while a reason still wants it; no-op when the label is not on the PR; lifting rides the same
mergeautonomy class as the add it reverses. Repository writers: head-scoped write, both fields cleared together (so a later human-applied label cannot inherit the bot’s provenance), absent reads as null. Executor: writes only for the manual-review label, clears on removal, never writes when the repo disabled the label.Draft eviction: draft does not block;
mergeableState: "draft"also evicts;false/absent unchanged; the next-oldest viable sibling still blocks; draft + manual-review compose.Changed-line coverage measured at zero uncovered statements and zero uncovered branches. Two unreachable defensive arms were removed rather than pragma-ignored (an "add already planned" guard that
manualHoldReason === nullalready excludes, and a?? ""on a required field).