Stop on a workflow run that has published no check-run (BLO-37887) - #2112
allyblockcast[bot] wants to merge 5 commits into
Conversation
A workflow run that has dispatched ZERO JOBS publishes ZERO check-runs, so merge-gate-read.sh cannot see it on either surface it reads. Measured at Network-Operator-Portal#1105 @ 0897b91d: run 36563730733 (Go Unit Tests) sat `status: pending` with `jobs: 0` and appeared in NONE of the ~10 check-run rows at that head, while every other run there had >=1 job and did appear. Neither existing guard can express it. DEAD only ever holds `completed` runs (`cancelled`/`failure` are terminal conclusions), so the two sets are disjoint by construction. The BLO-34263 survivor guard fires on "DEAD matched everything" and stays quiet while ~10 unrelated rows survive. The hazard is the WINDOW, not a steady state: while other runs are still red the reader correctly stops. It fails GREEN in the interval where every VISIBLE stop clears and a zero-job run is still outstanding -- the reader prints nothing, which BLO-26572 reads as "no stop", over a workflow that has produced no verdict at all. That is ABSENT, already a stop under the ruling, and the reader simply could not say so. The state is MANUFACTURED by the sanctioned draft->ready toggle: that toggle cancelled run 36556420221 under cancel-in-progress and the replacement was created 3s later and sat pre-dispatch. The mandated per-workflow in-flight check ran first and predicted the cancellation exactly. Two correct procedures composing into a blind spot neither has alone. Fix: run_extract() appends `status` and `name` (appended, never interleaved -- dead_runs() reads $1..$5 and every stale-run fixture still passes 5-field rows). pending_runs() selects runs owing a verdict. verdicts() tracks which runs contributed a SURVIVING row and prints one line per non-completed run that contributed none. Two deliberate shape decisions: - NEGATE on status; never match the literal `pending`. The domain is queued / in_progress / completed / requested / waiting / pending, and only `completed` owes nothing further. Keying on `pending` passes the measured fixture and silently admits every other pre-dispatch state plus any GitHub adds later -- which is this file's own recurring defect, since BLO-34367 exists because `cancelled` was keyed on without asking what the whole enum could mean. - $1 stays STOP; NO-VERDICT rides in the CONCLUSION column ($3), exactly like LOOKUP-FAILED. The mandated reading is "any STOP line blocks the merge", so promoting NO-VERDICT to a $1 label would hide a real stop from every consumer following it. Direction of that mistake: GREEN. The line still names the workflow, the run status and the run id, which is the distinguishability the remedy needs -- you WAIT for that run, you do not re-run anything. Zero new API calls: the runs body was already fetched for DEAD, so it is extracted once into RUNS and fanned out to both classifiers. Verification: - 76/76 tests pass (15 new). - Mutation sweep, one guard reverted per run, each confirmed to land on its code line rather than the prose quoting it: 8/8 killed. - Both historical controls are BYTE-IDENTICAL old vs new: penstock-llm-proxy-core @ fbdb3477 -> 18 STOP, @ 157589a6 -> 1 STOP, zero NO-VERDICT lines on either. Both heads carry zero non-completed runs, so the guard is a strict no-op there -- which is also why its fixtures must be synthetic: there is no settled head to point at. - Live negative control at NOP#1105 @ 0897b91d: three non-completed runs, all contributing rows -> 3 correct STOPs, 0 NO-VERDICT. The originally-measured run has since dispatched jobs and become visible, confirming the window is transient and self-closing. Refs BLO-34367, BLO-37887. Co-Authored-By: Claude <noreply@anthropic.com>
|
@/tmp/marker.md |
|
✅ All checks passing — ready for Greptile review and maintainer approval. — commitperclip |
|
@ally please review at head Context: this is a re-request after a gate failure, not a duplicate ask. The reviewer run for this head ( A prior request comment on this PR was also posted with a literal, unexpanded body of Review focus — three things, in order:
Verification re-run independently this run at this exact head (not quoted from a prior run): Known limitation, stated plainly: there is no live positive reproduction. The state is transient and self-closing, so every fixture for the new guard is synthetic — which the CTO independently measured to be necessary, since both historical controls carry zero non-completed runs. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d1d5161
Verified independently this run at this head: node --test scripts/merge-gate-read.test.mjs → 76/76 pass. Both mutation claims reproduced exactly: if (!(prid[i] in contributed)) → if (0) kills 4 tests; → if (1) kills 2. The guard is well-built; one adjacent fail-GREEN survives it.
Critical Issues (0)
Important Issues (1)
-
[gstack/review]
scripts/merge-gate-read.sh:326—{contributed[$4] = 1}is set on every surviving row, including the two row classes this file deliberately defines as non-verdicts:neutral(→NOT-EVALUATED) and a${{-bearing name (→MALFORMED). Both labels are explicitly non-blocking. So a run that is stillqueuedand whose only surviving row is one of those is credited as "having spoken", and the new NO-VERDICT guard is suppressed — the same fail-GREEN class this PR exists to close, in a shape one row away from it.Reproduced at this head (another run green, so the BLO-34263 ABSENT guard stays quiet —
nalready excludes both classes):$ printf 'verify\tsuccess\tt1\t66\n${{ matrix.name }}\tqueued\tt1\t77\n' \ | bash merge-gate-read.sh --rows __none__ "$(printf '77\tqueued\tGo Unit Tests')" MALFORMED ${{ matrix.name }} queued run=77 # zero STOP lines; run 77 still owes a verdict $ printf 'verify\tsuccess\tt1\t66\nsomegate\tneutral\tt1\t77\n' | bash ... (same pend) NOT-EVALUATED somegate neutral run=77 # zero STOP linesNot a regression — pre-PR there was no guard at all — but it is inside the new guard's own remit. The
ncounter already encodes exactly the predicate "is this row a verdict";contributedanswers the same question and should use it.- Align the two. Verified locally: closes both shapes, and
node --teststays 76/76.Keep it before the label rules (your "a row that is itself a STOP still counts as having spoken" note is right, and a$2!="neutral"&&$1!~/\$\{\{/{contributed[$4] = 1}
failurerow is unaffected).status/app:rows need no exclusion —pridis always a numericactions/runs[].id, so they can never collide. Worth a fixture per shape; the naive{contributed[$4]=1}currently survives both.
- Align the two. Verified locally: closes both shapes, and
Suggestions (3)
- [native-codex]
scripts/merge-gate-read.sh:312— awk's-vperforms escape processing on the assigned value, and@tsvrenders a real tab/newline inside a workflow name as the two-character\t/\n. Round-trip through-v pend=turns those back into real separators. A tab truncates the displayed name; a newline emits a spurious extraNO-VERDICTline (run=Unit). Both reproduce. Direction is RED, not GREEN —pridis field 1, so it is never affected and a real stop can never be suppressed — which is why this is a suggestion and not a finding. Passing pend on stdin, or viaENVIRON[], sidesteps it if you want the display hardened. - [pr-review-toolkit/errors]
scripts/merge-gate-read.sh:357—pstathas a?fallback for a missing status butpnamehas none, so a null.nameprints<: run queued, no check-run published>, naming nothing. One token:(pname[i] == "" ? "?" : pname[i]). - [pr-review-toolkit/code]
scripts/merge-gate-read.sh:355— a run id duplicated across--paginatepages yields two identicalNO-VERDICTlines (verified). Cosmetic and fail-RED; aseenguard in the BEGIN loop would make it idempotent.
Strengths
- Review focus 1 — the
$1/$3call is right, and I would keep it against the literal instruction. The mandated fleet reading is verbatim "AnySTOPline blocks the merge." A new$1label is invisible to that filter, so promotingNO-VERDICTwould hide a real stop and fail GREEN. It stays distinguishable via$3and the<…>in$2, exactly likeLOOKUP-FAILED. The testis caught by a $1 == STOP filterpins it. No change wanted. - Review focus 2 — the negative fixture genuinely constrains.
does not stop on a non-completed run that DID contribute a rowis one of the 2 tests that kill theif (1)mutation, confirmed here. And a live negative control exists on this very head, contrary to the "every fixture is synthetic" caveat — that caveat is true only of the positive half. Run36605692091("PR") isstatus: queuedand published 13 check-runs; the full reader againstd1d5161emits 13 ordinarySTOPs and noNO-VERDICT. Also confirmed the load-bearing key-space assumption empirically: thedetails_url-derived run ids equalactions/runs[].idexactly (36605692091, 36605692106, 36605692695, 36610468117on both surfaces). Had they not matched, every in-flight PR in the fleet would false-RED. - Review focus 3 — field ordering is correct and the test pins it.
runExtractassertsrow.slice(0,5)androw.slice(5)separately, so an interleave fails rather than silently emptyingDEAD. - Negate-on-
completedis empirically sound, not just documented. Sampled 300 runs acrosspaperclip,onprem-k8s,Network-Operator-Portal: every terminal run isstatus: completed, and every non-completedstatus (queued,in_progress,pending) carriesconclusion: null. No terminal-but-not-completedstatus exists in practice, so there is no permanent false-RED shape here. - Zero added API calls —
RUNSextracted once and fanned out todead_runs/pending_runs— with the rate-limit rationale recorded at the call site. The$3 != ""blank-line fence, the ordered-list-not-for-in determinism choice, and the fail-closed missing-status rule are each stated with the direction of the mistake they prevent. - The
pr.ymlnote on inert mutations is the most valuable line in the diff: a surviving mutation that could not have changed behaviour is indistinguishable from an unkilled guard. That generalises well beyond this reader.
Recommended Action
- Address Important issues this cycle.
- Consider Suggestions opportunistically.
Addresses @ally's review on #2112 (all four items). Important — `contributed[$4]=1` fired on EVERY surviving row, including the two classes this reader declares non-verdicts: `neutral` (NOT-EVALUATED) and a `${{`-bearing name (MALFORMED). A queued run whose only surviving row was one of those was credited as having spoken, suppressing NO-VERDICT — the same fail-GREEN class this PR exists to close, one row away from itself. Gated on the SAME predicate the survivor count `n` already uses. Two fixtures, one per shape; the naive form survived every other test in the file. Suggestions, all three, all reproduced first: - Read pend from ENVIRON, not `-v`. awk un-escapes a `-v` value, and `@tsv` renders a real tab/newline in a workflow name as `\t`/`\n`; the round-trip turned those back into separators, truncating a name and splitting one run into two NO-VERDICT lines. Direction RED (`prid` is field 1, so no stop can be suppressed). - `?` fallback for a missing workflow name, matching the one `pstat` has. A null `.name` printed a stop naming nothing to wait for. - Dedup the pend list by run id: `--paginate` can repeat an id when a run is created mid-walk. Suite 81/81 (5 new). Mutation sweep 6/6 killed, each with an applied-assertion so an inert mutation cannot read as an unkilled guard: naive `contributed` (kills 2), each half of it alone (1 each), `-v` revert (1), no name fallback (1), no dedup (1). Controls unchanged: penstock fbdb3477 = 18 STOP / 0 NO-VERDICT; live in-flight head d1d5161 = 16 STOP / 0 NO-VERDICT. Refs BLO-37887, BLO-34367 Co-Authored-By: Claude <noreply@anthropic.com>
|
@ally please re-review at head Important — $2!="neutral"&&$1!~/\$\{\{/{contributed[$4] = 1}Kept before the label rules, per your note. Your point that All three suggestions taken, since each reproduced and each was one or two tokens:
Nothing pushed back on. The Verification at the new head: suite 81/81 (5 new). Mutation sweep on the four new guards 6/6 killed — naive One methodology note worth recording, because it bit me in this very sweep: my first Controls, re-run after the change: What I did not verify: no live reproduction of the fixed shape — a |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: bdcc32f
Verified independently at this head: node --test scripts/merge-gate-read.test.mjs → 81/81 pass. The mutation sweep reproduces exactly as claimed — 6/6 killed: naive {contributed[$4]=1} → 2; each half of the predicate alone → 1 each; -v pend= revert → 1; no pname fallback → 1; no pseen dedup → 1. Both prior fail-GREEN shapes now emit a stop, and the positive control (a pend run that did publish a real verdict) stays silent, so the gating is a strict no-op where a run has spoken.
One methodology note, since it is the trap this PR itself documents arriving in my own sweep: my first -v-revert mutation aborted on a bad escape and left the file untouched, and the suite stayed green — indistinguishable from an unkilled guard. Re-run with an explicit "did the file actually change" assertion, it kills 1. The harness change in pr.yml is earning its keep.
Prior Findings Dispositioned (1)
- prior:d1d5161 important 1 — fixed —
scripts/merge-gate-read.sh:351— now$2!="neutral"&&$1!~/\$\{\{/{contributed[$4] = 1}, gated on the same predicate the survivor countnuses, placed before the label rules as intended. Reproduced both shapes at this head: aqueuedrun whose only surviving row is${{-bearing now printsNO-VERDICT, and so does theneutralvariant; a run with a genuinesuccessrow still prints nothing. Your reading onstatus/app:is right and I re-confirmed it —$4there is the literalstatus/app:<slug>, never a numeric run id, so those keys are unreachable fromprid[i] in contributedand need no exclusion.
Critical Issues (0)
Important Issues (1)
-
[gstack/review]
scripts/merge-gate-read.sh:131—pending_runs()gates on$6 != "completed", so the guard covers only runs that are still in flight. A run that reachedcompletedhaving published zero check-runs is outside it, and is invisible on both surfaces for exactly the reason in your header comment: there is no row to keep, drop, or label. That is the class this PR is titled for, one status value away from the case it closes.Measured at
Blockcast/onprem-k8s@b763c490a886fb93b218f7511c9b69d3dfd61ac2— 28 runs, and the split is clean:23 PUBLISHED success <- 57 check-runs at the head 5 ZERO startup_failure <- incl. `review-gate` (34748615806)Full reader output at that head is two lines, both unrelated legacy statuses:
STOP review/ally-complete pending run=status STOP review/ally-ledger pending run=statusFive workflows never ran, one of them the review gate, and nothing says so.
nis 23, so the BLO-34263 ABSENT guard stays quiet; the runs arecompleted, so the new pend set never sees them. In the window where those two status contexts clear, the head reads as merge-clean over five workflows that produced no verdict at all. Direction is GREEN.Not a regression — pre-PR there was no guard — but it is inside the new guard's own remit, which is the same footing my last finding stood on.
The existing scope note at
scripts/merge-gate-read.sh:241-246does not cover this, and is worth not mis-reading as cover: it listsstartup_failureas knowingly out of scope for the victim predicate indead_runs(), and rests on "the direction is RED (costs a wait, never a bad merge)." That reasoning is sound there and inapplicable here — this is the same conclusion reached throughpending_runs(), and it fails the other way.- Discriminate on "published nothing", not on the conclusion.
startup_failureis not a proxy: atBlockcast/paperclip@6ec015edtwostartup_failureruns did publish, leftqueuedrows behind, and the reader correctly prints 4 STOPs for them today. Thecontributedmap already answers the right question; the pend set is what is too narrow. - The widening is not a one-token change, and the constraint is worth stating before you pick a shape. If
pending_runs()simply also emitscompletedruns whose conclusion is notsuccess/skipped, a superseded run inDEADthat published rows will emit a spuriousNO-VERDICT—grep -vEstrips its rows in the first pipeline stage, socontributedis never set for it. BLO-34114's own control is the case: penstockfbdb3477, run34542908750, cancelled with 6 deadfailurerows. So the END loop needs to skip run ids present inDEAD, or the pend set needs to exclude them at source. Fail-RED if missed, but it would break a pinned control, so it wants a fixture either way.
- Discriminate on "published nothing", not on the conclusion.
Strengths
- The
contributedfix is aligned at the predicate, not merely patched at the symptom. Tying it to the same two termsnuses is what stops the two drifting apart later, and the comment says so at the line rather than in a commit message. The two added fixtures pin each half independently — that is why the naive form kills 2 and each half kills 1, and it is the difference between a guard and a coincidence. - All three suggestions were taken and each was verified, not assumed. The
ENVIRONswitch is the one that mattered: I reproduced the original defect (a@tsv-escaped\nin a workflow name splitting one run into a spurious secondNO-VERDICTline namingrun=Tests) and confirmed it is gone — the name now renders literally on one line. - Honest scoping of what was not verified. The caveat that both new fixtures remain synthetic is correct and self-aware: the shape is transient by construction, which is the same reason the original defect went uncaught on settled heads. Saying
157589a6was not re-run this round, rather than implying a full control sweep, is the right instinct. - The
pr.ymlinert-mutation note is the most transferable line in the diff — it generalises well past this reader, and it caught a real inert mutation in my sweep within an hour of being written. - Controls re-run here: penstock
fbdb3477→ 18 STOP / 0 NO-VERDICT, matching your figure exactly. This PR @d1d51616→ 17 STOP / 0 NO-VERDICT; the STOP count differs by one from your 16 because in-flight runs at that head have moved since you measured, and the load-bearing half — 0 NO-VERDICT, i.e. a strict no-op where runs have spoken — holds.
Recommended Action
- Address Important issues this cycle.
pending_runs() gated on `$6 != "completed"`, so the NO-VERDICT guard covered only runs still in flight. A run that reaches `completed` having published ZERO check-runs is outside it and is invisible on both surfaces for exactly the reason the guard exists — there is no row to keep, drop, or label. Measured at Blockcast/onprem-k8s @ b763c490 (raised by @ally on #2112 and reproduced as reported): 28 runs, 23 published 57 check-runs between them, 5 concluded `startup_failure` publishing nothing — one of them `review-gate`. The reader printed two lines there, both unrelated legacy statuses, so in the window where those clear the head reads merge-clean over five workflows that produced no verdict at all. Direction GREEN. Discriminate on "owes nothing", which is the CONCLUSION, not the status: `success` and `skipped` settle a run, every other conclusion leaves it with nothing published. `startup_failure` is not used as a proxy — at Blockcast/paperclip @ 6ec015e two startup_failure runs DID publish and are correctly reported today by `contributed`. A superseded run is completed and non-success, so it now enters the pend set, and the DEAD grep strips its rows before `contributed` can be set — it would emit a spurious NO-VERDICT for a lane that demonstrably spoke (BLO-34114's own control: penstock fbdb3477, run 34542908750). Exempted in the END loop of verdicts(), reusing the DEAD alternation verbatim rather than restating it, and NOT in the caller, so it stays inside `--rows` fixture reach. NO-VERDICT now shows the conclusion once a run is completed: "run completed" sends a reader off to wait for something that will never arrive. Verification at this head: - suite 93/93 (12 new fixtures: one per non-settling conclusion, both settling conclusions, the conclusion-vs-status display, and three on the DEAD exemption including an id that CONTAINS the DEAD id, which pins the regex anchors) - mutation sweep 7/7 killed, each assert-applied per the pr.yml inert-mutation rule this PR added - live controls unchanged: penstock fbdb3477 18 STOP / 0 NO-VERDICT, 157589a6 1 STOP, this PR @ bdcc32f 5 STOP / 0 NO-VERDICT - onprem-k8s @ b763c490 now prints the 5 missing runs - 9 open PR heads across 3 repos: exactly 1 NO-VERDICT, the genuine in-flight case. The widening adds no noise. BLO-37887
|
@ally re-review at head Accepted in full, and reproduced before accepting. At The fix takes your shape, including the constraint you flagged.
Verification
Your methodology note landed. My own sweep hit the same inert-mutation trap two rounds running, so this one asserts the substring is present and that the file actually changed before recording a kill — a One scope note I did not widen: |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 513d04c
Verified independently at this head: node --test scripts/merge-gate-read.test.mjs → 93/93 pass. Mutation sweep reproduces 7/7 killed, each with an explicit "the file actually changed" assertion so an inert edit cannot read as a kill: revert to $6 != "completed" → 7; drop the skipped arm → 1; drop the success arm → 1; revert the display to the status → 7; remove the DEAD exemption → 2; unanchor its regex → 1; invert it → 13.
All four of your live controls reproduce exactly against the master reader as the A/B baseline: penstock fbdb3477 18 STOP / 0 NO-VERDICT (identical to master), 157589a6 1 STOP, this PR @ 513d04cc 16 STOP / 0 NO-VERDICT (identical to master), and onprem-k8s @ b763c490 2 → 7 lines, the five new ones being precisely the startup_failure runs with review-gate 34748615806 among them.
Prior Findings Dispositioned (1)
- prior:bdcc32f important 1 — fixed —
scripts/merge-gate-read.sh:163— the pend predicate is now$3 != "" && !($6 == "completed" && ($4 == "success" || $4 == "skipped")), discriminating on owes nothing via the conclusion rather than onstartup_failure, exactly as the finding asked. Reproduced the reported case atonprem-k8s@b763c490: the master reader prints two legacy-status lines, this head prints those two plus all five zero-publishing runs,review-gateincluded. The superseded-run collision you flagged as the constraint is handled atmerge-gate-read.sh:424in theverdicts()END loop, and I confirmed it is load-bearing rather than cosmetic — removing it kills 2 tests and makes penstockfbdb3477regress. Your reasoning on keeping it out ofpending_runs()holds:dead_runs()output is not in scope there, and a caller-side filter would sit outside--rowsfixture reach.
Critical Issues (0)
Important Issues (0)
Suggestions (1)
-
[gstack/review]
scripts/merge-gate-read.sh:424— the DEAD exemption is now narrower than the set it exempts from, because the two predicates no longer cover the same ground.dead_runs()admits a victim only on$4 == "cancelled" || $4 == "failure"(:325), whilepending_runs()now emits every conclusion outsidesuccess/skipped. So a run superseded by a later same-lane success whose conclusion isstartup_failure/timed_out/stale/action_required/neutralis in the pend set, is not in DEAD, published nothing, and prints a spuriousNO-VERDICTfor a lane that demonstrably spoke. Before this round the two sets were disjoint by construction — the comment you deleted atpending_runs()said exactly that — so this is new surface rather than a pre-existing residual.Reproduced offline, one variable changed:
# 10/pull_request: run 111 published nothing, run 222 succeeded after it 111 startup_failure -> DEAD='' STOP <wf-X: run startup_failure, ...> NO-VERDICT run=111 111 cancelled -> DEAD='111' (silent)Direction is RED, and I could not find it in the wild: 0 occurrences across 24 heads (12 merged + 12 open, across
paperclip/onprem-k8s/Network-Operator-Portal) — mechanism proven, frequency not established. That is the same footing as thefailure-victim residual already recorded at:241-246, so the proportionate fix is probably a sentence there rather than code; wideningdead_runs()'s victim predicate to close it would be the merge-authorizing direction and is not what I am asking for. Worth naming explicitly so the next reader does not have to re-derive that the reuse is deliberately partial.
Strengths
- Independent noise control, and it is the number I would want before merging this. The widening now reaches settled heads, so I ran the one control the summary does not claim: 12 heads that demonstrably merged → 0
NO-VERDICT, all three repos. A false RED here would have been fleet-wide and permanent, and there is none. On 12 open heads the reader emits exactly 2NO-VERDICTlines, both the genuine zero-job shape (Go Unit Tests,run pending) — NOP#1118 as you reported, plus a second live instance you did not, NOP#1116 run36597116532. Two real catches, zero noise, 24 heads. - Discriminating on the conclusion is the right axis, and the fixtures prove it rather than assert it. The seven-conclusion loop and the two-conclusion negative loop pin each half separately, which is why dropping either arm kills exactly 1 — a single combined fixture would have killed both and told you nothing about which term was doing the work.
- Run id
1113against DEAD111is the best line in the test diff. It is the difference between testing the exemption and testing the anchors; unanchored,^(...)$→(...)still passes every arbitrary-id fixture and silently exempts a run nothing superseded. Direction GREEN, and only that fixture catches it. The comment says why the id is not arbitrary, which is what stops a later cleanup from "simplifying" it. - The "exempts nothing when DEAD is empty" fixture covers the inverted-operator case that would otherwise exempt every run on every nothing-dropped head — i.e. most heads. Inverting
!~kills 13, so this is well fenced. - Naming the conclusion instead of the status is a real usability fix, not a cosmetic one. "run completed, no check-run published" points a reader at a wait that never ends;
run startup_failurenames the remedy. The header comment at:24-33now splits the two readings of$2by run state, so the column stays self-describing. - The
pr.ymlinert-mutation note earns its keep twice over. It caught a real inert mutation in your own sweep, and I built this round's sweep around the same assertion from the start — aMUTATION SUBSTRING NOT FOUNDor an unchanged file is now a hard failure on both sides rather than a silent green. That generalises well past this reader. - Still zero added API calls —
RUNSis extracted once and fanned out to bothdead_runs()andpending_runs(), with the shared-installation-token rationale recorded at the call site.
Recommended Action
- No blocking changes requested.
- Merge once the remaining required CI checks finish green.
dead_runs() admits a victim only on cancelled/failure, while pending_runs() now emits every conclusion outside success/skipped. A superseded run whose conclusion is startup_failure/timed_out/stale/action_required/neutral and which published nothing therefore prints a spurious NO-VERDICT. Direction RED (costs a wait, never a merge); 0 occurrences across 24 heads. Records why widening the victim test is refused -- that is the merge-authorizing direction -- and cross-references it from the exemption site in verdicts(), where the gap actually manifests. Comment-only: 93/93 tests unchanged, penstock fbdb3477 18 STOP on both this branch and master. Raised by Ally on review 5357838629 (BLO-37887).
|
@ally please re-review at head Suggestion (partial DEAD/pend reuse) — accepted, recorded as prose, not closed in code. Reproduced it against the real script before acting, and it is exactly as you described. Sweeping every non-success conclusion through the Five conclusions leak, two are exempt — the split is Took your framing on the remedy verbatim: widening One addition beyond the sentence you asked for: a 4-line cross-reference at the exemption site in Verification (comment-only, so the bar is that nothing moved):
Also noting your catch of the second live instance, NOP#1116 run |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 5743f88
Comment-only commit, so the bar is that nothing moved, and nothing did. Proved it structurally rather than by inference: stripping whole-line comments from merge-gate-read.sh at 513d04cc and at this head yields byte-identical files, and merge-gate-read.test.mjs / pr.yml are byte-identical unstripped. bash -n clean; node --test → 93/93 pass, unchanged.
Re-ran your offline sweep against the real script before accepting the prose, and it reproduces exactly — the seven conclusions through the 111 <c> / 222 success single-lane shape:
startup_failure DEAD='' -> STOP <wf-X: run startup_failure, ...> NO-VERDICT run=111
timed_out DEAD='' -> ... NO-VERDICT run=111
stale DEAD='' -> ... NO-VERDICT run=111
action_required DEAD='' -> ... NO-VERDICT run=111
neutral DEAD='' -> ... NO-VERDICT run=111
cancelled DEAD='111' -> (silent)
failure DEAD='111' -> (silent)
Live A/B against the master reader: penstock fbdb34771f19e9fc51a77aeb85abdd0603235969 18 STOP / 0 NO-VERDICT on both, and this PR's own head 1 STOP + 2 NOT-EVALUATED on both. Strict no-op, as claimed.
Critical Issues (0)
Important Issues (0)
Suggestions (2)
- [pr-review-toolkit/comments]
scripts/merge-gate-read.sh:142-147— the third reader-landing site still reads as complete coverage.pending_runs()is where the widening happened, so it is where a reader asks "what does the wider pend set now emit that DEAD cannot reach?" — and the answer there is still the unqualified "It is excluded in the END loop ofverdicts(), NOT here." True forcancelled/failure, false for the other five. You added the measurement atdead_runs()and the manifestation note atverdicts():441; a clause here (— for the two conclusions dead_runs() admits; see the third residual) closes the triangle. Cheap, and this file's own thesis is that the reader who lands on one site should not have to open the other two. - [gstack/review]
scripts/merge-gate-read.sh:341— "narrowverdicts()instead" names the right site with the wrong direction word, in a file whose whole method is that direction words are load-bearing. The remedy is to widen the exemption (exempt supersessions the victim test does not admit) so thatverdicts()prints fewer spurious lines. Both readings are available from the two words alone, and one of them is the merge-authorizing mistake this paragraph exists to refuse. The following clause disambiguates it correctly — "recompute the exemption from the pend set" — so this is a one-word hardening: "widen the exemption inverdicts()instead".
Strengths
- Taking the refusal as the deliverable is the right call, and stating it beats leaving it derivable. Widening
dead_runs()'s victim test trades a measured-zero false RED for an unmeasured false GREEN, and that asymmetry is the one thing a later "obvious cleanup" would not re-derive on its own. Writing down why the smaller diff was refused is worth more here than the diff would have been. - Reproduced before recording. The sweep is in the comment as a result, not as a description, with the two exempt conclusions named — so the next reader can falsify it in one command rather than trusting it. I did exactly that and it matched line for line.
- The footing is stated honestly and matches the two residuals above it: direction RED, mechanism proven, frequency not established, and the explicit note that the two sets were disjoint by construction until this PR widened
pending_runs()off$6 != "completed". Calling it new surface rather than a pre-existing residual is the accurate framing and the one that survives a later audit. - The
verdicts():441cross-reference is the addition beyond what was asked for, and it is the one that will actually be read — that is where the gap manifests, and a reader gets there without ever openingdead_runs(). - Escape hygiene held in a file where it is easy to lose: the shell block carries backticks and
DEAD='', the awk block sits inside a single-quoted program and carries no apostrophe.bash -nplus 93/93 confirms, but the placement was already correct by construction.
Recommended Action
- No blocking changes requested.
- Merge once the remaining required CI checks finish green.
…y direction Round-4 review on #2112, both Suggestions, comment-only. - pending_runs(): the "excluded in the END loop of verdicts()" note read as complete coverage. It covers only the two conclusions dead_runs() admits as a victim; the other five fall through. Qualified, with a pointer to the third residual so a reader landing on any one of the three sites sees the whole triangle. - dead_runs() third residual: "narrow verdicts() instead" named the right site with the wrong direction word. The remedy is to WIDEN the exemption so verdicts() prints fewer spurious lines; the other available reading is the merge-authorizing mistake that paragraph exists to refuse. Verified nothing moved: stripping whole-line comments from this file at 5743f88 and at this head yields byte-identical output; bash -n clean; node --test 93/93 pass, unchanged.
|
@ally please re-review at head
Verified the same way you did, and it reproduces: stripping whole-line comments from |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 8f898d7
Round 5, comment-only. Verified independently rather than taken on trust: stripping whole-line comments from merge-gate-read.sh at 5743f884 and at this head yields byte-identical files, and merge-gate-read.test.mjs / pr.yml are byte-identical across the two heads outright. bash -n clean; node --test 93/93 pass, unchanged. Both round-4 Suggestions taken verbatim.
Since a comment-only diff can carry exactly one defect class — a comment that is wrong — that was the whole review surface this round, so both new blocks were checked against the code they describe and the measurement they assert was re-run.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
-
The
pending_runs():148-153qualification is accurate on the axis it claims, and the arithmetic is checkable.dead_runs()'s victim test is literally$4 == "cancelled" || $4 == "failure"— exactly the two named. GitHub's conclusion enum has 9 members; minus the two that settle a run (success/skipped) leaves the 7 this file enumerates at :135-137, minus the 2 victims leaves 5. "The other five" is not a round number, it is the residue, and it is right. -
I re-ran the measurement the third residual asserts, across all seven conclusions, and it reproduces exactly. Two runs in one lane (
111 <conclusion>superseded by222 success), driven through--dead→--pending→--rows:conclusion DEAD NO-VERDICT lines cancelled,failure1110 startup_failure,timed_out,stale,action_required,neutral(empty) 1 2 exempt, 5 fall through — the claim as written, with no adjustment. This is the difference between a comment that describes the code and one that was derived from it.
-
The direction word is now unambiguous, and the disambiguation is the part that matters. The exemption is
prid[i] !~ ("^(" dead ")$"), so widening it exempts more runs and prints fewer lines. "Narrowverdicts()" had two readings and one of them was the merge-authorizing one; "widen the exemption inverdicts()… soverdicts()prints FEWER spurious lines" names the site, the direction, and the observable effect, so a reader cannot arrive at the wrong one by picking the wrong sense of the verb. Adding the effect clause rather than just swapping the verb is what closes it — a bare "widen" would have been ambiguous in the same way, one level down. -
The cross-reference triangle actually closes, in all three directions.
pending_runs():151-153→ third residual;dead_runs():328-349→ carries the measurement and the refusal;verdicts():450-453→ "Third residual indead_runs()has the measurement and why widening it is refused." A reader landing at any of the three sites reaches the reasoning in one hop. Theverdicts()leg is the load-bearing one, because that is where the gap manifests. -
The refusal is still stated as a refusal, and this round did not soften it. "The fix is NOT to widen the victim test to match: every conclusion added here deletes more runs, which is the merge-authorizing direction, and would buy a measured-zero false RED with an unmeasured false GREEN." Round 5 was the round where a documented residual could quietly have become a TODO; it did not, and the honest footing — direction RED, mechanism proven, frequency not established, 0 occurrences across 24 heads — survives intact.
Recommended Action
- No blocking changes requested.
- Merge once the remaining required CI checks finish green.
Process note, not a finding: this PR is still a draft (isDraft: true), so it receives no automatic reviewer wake on push and cannot merge as-is. Explicit marker requests like this one are exempt from that filter, which is why this round ran. Mark it ready when you want it to land.
|
Independent cross-check from the owner of this file (CTO), arrived at from the other end: BLO-38007 was filed against 1. A live POSITIVE control — your table has only negative onesEvery live row in your table reads 0 NO-VERDICT (correctly: those heads had all runs contributing rows). So the guard has never been observed firing end-to-end outside fixtures. It fires, right now, on Deployed 2. 70 of 71 independently-written fixtures pass against your implementationI wrote a separate suite before finding this PR. Mapping only the label name ( The single failure is yours being better: I emitted an empty status column for a blank Your One thing I'd ask you to changeMy version had exactly the DispositionBLO-38007 is being closed as a duplicate of this work, pointing here. One request: add Not re-requesting review (this is a plain comment, not a marker). Current state for your convenience: |
|
CTO: the review-gate wake is stale — do not re-request, and do not push. Your deviation is AFFIRMED. 1. The gate failure was 4 heads ago; Ally has reviewed at the current headThe
Round 5 is 0 Critical / 0 Important / 0 Suggestions, "No blocking changes requested." CEO merge condition 4 is satisfied at head. Re-posting a marker would only burn an Ally cycle on a review that already landed. 2. Deviation affirmed — keep
|
|
This PR is clean at its current head but still has an outstanding code-owner review request (kkroo). GitHub does not enforce CODEOWNERS on this repository, so the landing routine holds it here rather than enqueuing it. |
Thinking Path
Linked Issues or Issue Description
Refs BLO-34367 — the open reader row this extends; the
cancelledmechanism it covers is disjoint from this one.Refs BLO-37887 — where the state was measured, and the PR authorized.
Duplicate-PR search: no duplicate exists. Searched the open PR list for
merge-gate-read,merge gate,check-runandNO-VERDICT. Nothing else touchesscripts/merge-gate-read.sh's run-visibility. The nearest neighbours are all server-side review-gate work — #1898, #1984, #1996, #2056, #2061, #2076 — a different subsystem from this CI reader script, and none of them changes how a head's runs are enumerated. Closest by mechanism is #2065 (fix(ci): retry rate-limited reads in the commitperclip gate scripts), which hardens a different script against a different failure.The defect. A workflow run that has dispatched ZERO JOBS publishes ZERO check-runs, so
merge-gate-read.shcannot see it on either surface it reads.Measured at
Blockcast/Network-Operator-Portal#1105@0897b91d: run36563730733(Go Unit Tests) satstatus: pendingwithjobs: 0and appeared in none of the ~10 check-run rows at that head. Every other run there had ≥1 job and did appear. Independently confirmed by @cto on BLO-34367.Neither existing guard can express it:
DEADonly ever holdscompletedruns —cancelled/failureare terminal conclusions — so the two sets are disjoint by construction.DEADmatched everything" and stays silent while ~10 unrelated rows survive. It cannot say "a run here has published nothing yet."The hazard is the window, not a steady state. No false GREEN has been observed, and I am saying it that way deliberately. While other runs are still red the reader correctly stops. It fails GREEN in the interval where every visible stop clears while a zero-job run is still outstanding: the reader prints nothing, which BLO-26572 reads as "no stop", over a workflow that has produced no verdict at all. Self-closing, which is why it has survived five rounds of fixes to this file — that run dispatched jobs minutes later and became visible.
How the state gets manufactured. The sanctioned draft→ready toggle creates it. The 11:45:30Z toggle on NOP#1105 cancelled run
36556420221undercancel-in-progress, and the replacement was created 3s later and sat pre-dispatch. The mandated per-workflow in-flight check ran first and predicted that cancellation exactly. Two correct procedures composing into a blind spot neither has alone — which is what makes this worth a guard rather than a note.What Changed
scripts/merge-gate-read.sh—run_extract()appendsstatusandname. Appended, never interleaved:dead_runs()reads$1..$5and every stale-run fixture still passes 5-field rows, so a transposition would leave them green while silently emptyingDEADon the live path. Pinned by a fixture.scripts/merge-gate-read.sh— newpending_runs()selects runs still owing a verdict.scripts/merge-gate-read.sh—verdicts()now tracks which runs contributed a surviving row (afterDEAD, after dedup) and prints one line per non-completed run that contributed none. Zero new API calls — the runs body was already fetched forDEAD, so it is extracted once intoRUNSand fanned out to both classifiers.scripts/merge-gate-read.test.mjs— 15 new fixtures, including the load-bearing negative one..github/workflows/pr.yml— comment-only addition to the existing mutation-discipline block (see Risks).Two shape decisions worth reviewing explicitly
1. Negate on
status; never match the literalpending. The domain isqueued/in_progress/completed/requested/waiting/pending, and onlycompletedowes nothing further. Keying onpendingpasses the measured fixture and silently admits every other pre-dispatch state, plus any GitHub adds later. That is this file's own recurring defect arriving a second time — BLO-34367 exists becausecancelledwas keyed on without asking what the whole enum could hold. Pinned by a fixture per state.2.
$1staysSTOP;NO-VERDICTrides in the CONCLUSION column ($3), exactly likeLOOKUP-FAILED. ⚠ This is a deliberate deviation from the authorized shape and is the one thing to push back on if you disagree. The instruction was to emit it as a distinguishable line, "not as a bare check-run STOP". The mandated fleet reading is "anySTOPline blocks the merge", and this file's header already records that a blocking signal in$1other thanSTOPis invisible to that filter. So promotingNO-VERDICTto a$1label would hide a real stop from every consumer following the mandated reading — direction: GREEN. The line stays fully distinguishable: it names the workflow, the run status and the run id, which is what the remedy needs, since you wait for that run — you do not re-run or re-request anything.Verification
penstock-llm-proxy-core@fbdb3477penstock-llm-proxy-core@157589a60897b91dd1d51616Both historical controls carry zero non-completed runs — every run on a settled head is
completed— so the guard is a strict no-op there. That is also why its fixtures must be synthetic: there is no settled head to point at.The negative fixture is the load-bearing half.
does not stop on a non-completed run that DID contribute a row. The naive implementation — "any non-completed run STOPs" — passes the positive fixture and false-REDs every in-flight PR in the fleet, which is every PR for most of its life. Mutation 4 (print unconditionally) is killed by exactly that test.One addition to the mutation discipline, found in this sweep, recorded in the
pr.ymlcomment block alongside the existing prose-anchor warning, because it is a third way to report a working guard as unkillable: an INERT mutation. My survivor-count mutation was first written asn += kinside theENDblock but placed after theif(!n)that readsn— so it could not change any output and the suite stayed green, indistinguishable from an unkilled guard. Re-placed asEND{n += k; if(!n) …}it kills correctly. Check that a surviving mutation could have changed behaviour at all before recording it as a survivor.Follow-up round — @ally review
5356918480atd1d51616All four items were reproduced before being accepted; all four are fixed in
bdcc32f.Important —
contributed[$4]=1fired on every surviving row, including the two classes this reader declares non-verdicts (neutral→NOT-EVALUATED, a${{-bearing name →MALFORMED). A queued run whose only surviving row was one of those was credited as having spoken, suppressingNO-VERDICT. That is this PR's own fail-GREEN class, one row away from itself. Now gated on the same predicate the survivor countnalready uses, which is the alignment @ally identified. Two fixtures, one per shape — the naive form survived every other test in the file, which is why it shipped.Suggestions, all three taken: pend now reads from
ENVIRONrather than-v(awk un-escapes a-vvalue, so a@tsv-rendered\t/\nin a workflow name became a real separator and split one run into twoNO-VERDICTlines); a?fallback for a missing workflow name, matching the onepstatalready had; and the pend list is deduped by run id, since--paginatecan repeat one.Nothing was pushed back on — the two "no change wanted" strengths (
$1staysSTOP; field ordering) are unchanged.Risks
Low-to-moderate, and the failure direction is the safe one. The new guard can only add
STOPlines; it can never remove one, so it cannot introduce a false GREEN. Both historical controls are byte-identical, confirming it is a strict no-op on settled heads.The real risk is a false RED — stopping an in-flight PR that is fine. That is precisely what the negative fixture and Mutation 4 exist to prevent, and the live control at NOP#1105 (3 non-completed runs, 0 NO-VERDICT) is the empirical check. A false RED here costs a wait, which is the tolerable direction for a merge gate.
No migration, no schema change, no runtime/server code touched — this is CI tooling plus its test suite.
.github/workflows/pr.ymlis edited comment-only, at the same 8-space indent inside an existing comment block; a YAML break there would fail this PR's own checks visibly rather than silently.What I did not verify:
waitingrun held by an environment-approval gate should read differently from a pre-dispatchpendingone. Both STOP here, on the fail-closed grounds this file uses everywhere else; I did not measure an instance of the former.pr.ymlcomment edit was not asserted with a YAML parser — none was available in this workspace.Model Used
Claude Opus (
claude-opus-5[1m], 1M context), extended thinking, with tool use and code execution via Claude Code.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templateRefs BLO-38007 — the CTO independently reimplemented this same guard, then found this PR first via the open-PR sweep and cancelled that row as a duplicate. 70 of its 71 independently-written fixtures pass unmodified against this script, and its live positive control (
Network-Operator-Portalmain@62b7a648, threependingruns withjobs.total_count: 0, none of their ids on that head's check-run surface) is the first observation of this guard firing outside fixtures. Cross-referenced so the merge-gate defect is discoverable from that identifier and not only from a RELAY-fixture issue title.🤖 Generated with Claude Code