Skip to content

fix(gate): do not lift the manual-review hold while another hold is live - #9943

Merged
JSONbored merged 1 commit into
mainfrom
fix/manual-review-latch-9939
Jul 30, 2026
Merged

fix(gate): do not lift the manual-review hold while another hold is live#9943
JSONbored merged 1 commit into
mainfrom
fix/manual-review-latch-9939

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Follow-up to #9942, which landed the #9939 fix while this was in flight. This PR no longer duplicates it — it was reset onto main and now fixes one gap in the released version.

The gap

#9942 guards the release on manualHoldReason === null, and its comment states the case cannot arise:

Guarded on manualHoldReason === null, i.e. NO reason wants a hold this pass — which is why this cannot lift a label applied for reason A just because reason B cleared.

No "is an add already planned this pass?" guard: every site that ADDS this label requires a live hold … and all of those set manualHoldReason … A guard here would be an arm no input can reach.

manualHoldReason covers only the guardrail hit, unverified CI, an action_required conclusion, and a not-review-good verdict. Every would-MERGE hold fires on reviewGood with a success conclusion — precisely when manualHoldReason is null.

Verified against the merged planner, before changing anything

Driving planAgentMaintenanceActions from main (35fceed) with the label present, provenance set, and one hold live:

live hold release planned on main?
migrationCollisionHold YES
unlinkedIssueMatchHold YES
priorityEligibilityHold YES
unlinkedIssueMatchClose YES
unstable merge state YES

It is removed once and not re-added on the next pass (with review_state_label acting, the re-add sites are skipped in favour of the sibling disposition label). So this is not a flap — it is a one-way strip.

Why that matters: the manual-review label is the exact thing the executor checks to deny merge and approve. Removing it while the hold still stands removes the enforcement for a hold nothing has resolved. Whether a merge actually follows depends on the rest of the plan — I am not claiming these PRs merged — but the block that was supposed to stop them is gone.

The fix

One named predicate listing every reason that would ADD the label, so a hold added later cannot silently fail to suppress the release:

const noManualReviewHoldWanted =
  manualHoldReason === null &&
  input.migrationCollisionHold === undefined &&
  input.unlinkedIssueMatchHold === undefined &&
  input.priorityEligibilityHold === undefined &&
  input.unlinkedIssueMatchClose === undefined &&
  !mergeableStateUnstable &&
  !heldForManualReview &&
  !mergeTerminallyBlocked;

Everything else in #9942 is untouched — the provenance column, the SHA-agnostic release, the human-applied-label refusal.

Validation

  • 7 new tests: one per live hold, plus the counterweight — with nothing holding, the label is still released, so the one-way latch fix(gate): lift the bot's own manual-review hold, and stop drafts wedging the merge train #9942 fixed does not come back. Plus the no-provenance refusal.
  • Mutation-verified: reverting the predicate to manualHoldReason === null fails exactly those 5 and leaves the counterweight passing, so they are pinning the real behaviour and the guard is not merely over-broad.
  • test/unit/agent-actions.test.ts: 343 passing.
  • Full checker sweep + suite results appended below once they finish.

@loopover-orb

loopover-orb Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 30, 2026
#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.
@JSONbored
JSONbored force-pushed the fix/manual-review-latch-9939 branch from 641a57b to f1e1e42 Compare July 30, 2026 19:26
@JSONbored JSONbored changed the title fix(review): release a bot-applied manual-review hold when its reason clears fix(gate): do not lift the manual-review hold while another hold is live Jul 30, 2026
@JSONbored

Copy link
Copy Markdown
Owner Author

Validation complete on the final tree:

@JSONbored
JSONbored merged commit c7eabde into main Jul 30, 2026
6 checks passed
@JSONbored
JSONbored deleted the fix/manual-review-latch-9939 branch July 30, 2026 19:32
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.00%. Comparing base (35fceed) to head (f1e1e42).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9943      +/-   ##
==========================================
- Coverage   91.88%   91.00%   -0.88%     
==========================================
  Files         928      928              
  Lines      113670   113671       +1     
  Branches    27411    27412       +1     
==========================================
- Hits       104440   103449     -991     
- Misses       7931     9118    +1187     
+ Partials     1299     1104     -195     
Flag Coverage Δ
backend 94.11% <100.00%> (-1.56%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/settings/agent-actions.ts 98.28% <100.00%> (+<0.01%) ⬆️

... and 3 files with indirect coverage changes

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