Repository navigation
tools: the merge gate reads a merged PR as green, and a skip can outrank a failure #16046
Description
Two blind spots in scripts/pr_required_gate.py
Both found by using the gate, not by reading it. PR opened alongside this issue.
1. A merged PR reads CONTEXTS-GREEN. Its required contexts all succeeded — before it landed. The verdict is true and useless, and read as clearance it says "ready to merge" about something already merged.
That is not hypothetical: I read it as clearance and told another session a merge window was open, alongside three PRs that had merged hours earlier.
2. A skip can outrank a conclusive failure. The gate takes the newest observation per context name. That is right for supersession — one workflow re-run leaving cancelled behind a success — and wrong for two workflows publishing one context name, where a path-filtered shim can land skipped AFTER a real failure on the same commit.
Observed on #16027: the gate reported green while the merge button stayed red. The tool disagreeing with the thing it exists to predict.
The property underneath, which matters more than either fix
These are the third and fourth states this tool could not see. The others:
- pending contributing to no bucket, so a histogram of pass/fail cannot see a suite that has not started
- a conflicted PR producing no runs at all, which reads identically to "CI has not started"
- a superseded
cancelledtaken as the current answer
Four symptoms, one design property: a check that enumerates conditions is blind to the conditions it does not enumerate, and every blind spot reads as success.
The corollary is the part that should shape the code rather than sit in an issue: the design has to assume there is a fifth. A verdict that cannot say "I do not know what I did not look at" will always spend its blind spots as green — which is the same failure as a reach floor bound to findings rather than to what was examined.
Acceptance criteria
- A merged or closed PR reports a non-green verdict naming its state, and exits non-zero
- A skip never overrides a conclusive result in either ordering — the API returns runs unordered, and the case where the skip is seen first is the one a naive fix misses
- Supersession still resolves to the newest conclusive observation, so
cancelled→successandfailure→successare unaffected - A lone skip is still the answer for a path-filtered context nothing else reported — otherwise "a skip is never authoritative" becomes "path-filtered contexts never report"
- Each fix has a contrast: an OPEN PR with green contexts is still green, and the skip rule does not make the gate refuse everything
- The blind-spot property is stated where a future editor reads it, not only in this issue
Note for whoever extends it next
A mutation that ignored skips entirely passed every test written for finding 2, because all of them put the conclusive run later in the list. The unordered-API ordering is the only path to the promotion branch. Worth assuming the same is true of the fifth blind spot: the test that would catch it is probably one nobody has thought to write, not one that exists and fails.
Activity
feat(tools): the gate names the preconditions it does not examine (#1…
fix(tools): the merge gate reported a merged PR as green, and a skip …
feat(tools): the gate names the preconditions it does not examine (#1…
fix(tools): the merge gate's blind spots, and naming the ones that re…
mrveiss commentedon Sep 8, 2026
Closing. All six criteria verified against merged base (
7a647890f6, PR #16052), each with the code and the named test that proves it.origin/Dev_new_guipr_required_gate.py:417-418→NOT-OPEN ({pr_state}).pr_required_gate_test.py:363test_a_merged_pull_request_is_not_reported_as_mergeableassertsgate.main(["123"]) == 1at:391andNOT-OPEN (MERGED)in the output at:392_INCONCLUSIVEat:73; both directions handled at:108and:110. Tests:311test_a_skip_never_overrides_a_conclusive_result_whatever_the_orderand:417test_a_conclusive_result_wins_even_when_the_skip_is_seen_first:334test_supersession_is_still_newest_wins, plus:34test_a_superseded_cancelled_before_a_success_reads_green:354test_a_skip_is_still_the_answer_when_nothing_else_reported— "A path-filtered context that only ever skips is legitimately green":395test_an_open_pull_request_with_green_contexts_is_still_greenfor the state fix;:354is the contrast for the skip rule, so it does not become "path-filtered contexts never report"pr_required_gate.py:303-304and:346-352, in the module, not only hereOn the note for whoever extends it next. The warning was that a mutation ignoring skips entirely passed every test written for finding 2, because all of them put the conclusive run later.
:417is the test that closes that specific path — it is the ordering the unordered API produces and the only route to the promotion branch.What I am not claiming. Criterion 1 was found by using the gate — I read
CONTEXTS-GREENon merged PRs as clearance and told another session a merge window was open. That is fixed and tested. But the issue's own corollary is that there is a fifth blind spot, and closing this does not mean the tool now sees everything; it means the four named states are handled and the verdict says what it did not examine (:436test_the_verdict_names_what_it_did_not_examine). I have used theNOT_EXAMINEDline in every sweep today, and it is what stopped me merging on contexts alone — threads are still checked separately, by hand.docs(tools): check_run_status cites #16046, not the unrelated #16040 (#…
Merge pull request #16860 from mrveiss/fix-16046-citation