fix(ship-it): Step 3z asserts its own zero-runs precondition before it nudges (#4830) - #4853
Conversation
…t nudges (#4830) Step 3z is the one ship-it step that both takes a destructive server-side action on a live PR — close→reopen, re-running the full suite — and trusted its caller for the state that action remedies. CONTEXTS / NRUNS never reached the script; the branch that selects it lives in SKILL.md prose, so a mis-read branch was the only thing between a green PR and a close→reopen plus a durable comment claiming a dropped trigger that never happened (PR #4816: 45 contexts, 33 runs, nudged anyway). The script now re-derives the three numbers that define the state at the head — `.contexts` relayed from `pipeline-cli checks read` (ADR 0228: relay the one rollup reader, never derive a second rollup here), plus the workflow-count and head-run REST reads — and refuses with a third terminal line, `refused (not the dropped-trigger state: CONTEXTS=<n> NWF=<n> NRUNS=<n>) — no nudge`, without touching the PR: no state=closed / state=open, no comment. An unreadable count refuses on the same line (`…=unreadable`) instead of passing as zero — a check that cannot see its subject answering confidently anyway is the whole class (#4482). Both pre-existing outcomes are unchanged, as are the once-per-head exhaustion bound (which still leaves its durable #1928 comment) and the dual-mode / no-EXIT-trap shell shape. verify-step3z-precondition.sh pins this executably over six head states, hermetic against stubbed gh / pipeline-cli, asserting the PR was NOT mutated on every refusal. It carries its own mutant — the precondition block deleted — and requires that mutant to nudge the green head, so an assertion that stops having teeth reds instead of passing quietly. Wired into the `skills` CI job beside the other ship-it chain pin. Fixes #4830
🚀 Preview deployed
|
|
review-code: advisory — blocking-set PR (§CP — approval-gated) PR #4853 is §CP — Reviewed-head: @ 5a2be93 Required namespaces for this diff ( Read the PR head (§HEAD): every file under review was sourced from Verified PR #4853 against the acceptance criteria of #4830, one at a time — all pass:
Run-evidence bundle: PRESENT for head Falsification log — what I ran, not what the PR claims. Harness at head: PASS, 7/7 (6 states + its own mutant). Then three mutations of my own against a scratch copy of the head files:
The harness's own mutant generator was checked for the vacuity it guards against: its Non-blocking observations (no action required, not gating):
All criteria pass. review-code does not merge — under §CP, a Verdict-written: 2026-08-03T08:41:37Z |
|
review-skill: advisory — blocking-set PR (§CP — approval-gated) PR #4853 edits the agent control plane: Reviewed-head: @ 5a2be93 Required namespaces for this diff ( Skill text was read from the PR head Verified against #4830's acceptance criteria + the skill-rigor checklist — all checks pass: Acceptance criteria
Skill-rigor checklist
Falsification log. I did not take the harness's own PASS as evidence that it has teeth. Harness at head: PASS, 7/7. Then, against a scratch copy of the head files:
Its self-generated mutant is also non-vacuous: the Non-blocking note (not gating): the harness asserts the recorded call log for Every acceptance criterion and every rigor check passes. review-skill does not merge — under §CP this is approval-gated; Verdict-written: 2026-08-03T08:43:15Z |
|
ship-it: merge-queue ejection detected — PR #4853 was enqueued (09:12:22Z) but the queue dropped it without merging at 09:20:24Z (still open, no longer queued, not merged; Batch: Failing suite/test — new, and different from the first ejection's cause: The first ejection's cause (#4847, Note this PR's own head CI is fully green (45 contexts, zero gating red) and the run-evidence bundle is SHA-bound all-pass at Routing back to repair/re-queue — this is NOT a shipped state. ship-it did not re-enqueue and left no armed merge intent. |
ship-it's Step 3z is the one step that close→reopens a live PR to re-fire dropped CI, and until now it took that action purely on the caller's word — the numbers that define the dropped-trigger state never reached the script, so the only thing between an agent mis-reading Step 3's branch list and a real PR being close→reopened was the agent reading correctly. On PR #4816 it did not: a head with 45 check contexts and 33 workflow runs was close→reopened and given a durable comment claiming it had zero runs.
The script now establishes that state itself before it touches the PR, and refuses with a named third outcome when it cannot — including when a lookup fails, since an unreadable count is never a confirmed zero.
Fixes #4830
What changed
step3z-dropped-trigger.shasserts its own precondition. It re-derives the three numbers at the head —CONTEXTSrelayed frompipeline-cli checks read(ADR 0228: relay the one head-CI rollup reader, never derive a second rollup here), plus theactions/workflowsandactions/runs?head_sha=counts the sibling probe reads — and only nudges when the state SKILL.md Step 3 branch 3 defines actually holds: zero contexts, the repo runs Actions, zero runs for this head.refused (not the dropped-trigger state: CONTEXTS=<n> NWF=<n> NRUNS=<n>) — no nudge— nostate=closed/state=open, and no comment (a comment here would be the same false claim docs(decisions): record the release mechanism — release-please manifest mode (#4799) #4816 left behind). An unreadable count prints on the same line as…=unreadableand refuses, which is the whole point of the class in The 'check runs, exits clean, observed the wrong surface' defect class has no citable home — artifacts cite a closed, narrower instance #4482.disarm_intent refuseon every exit, and the dual-mode / no-EXIT-trap shell shape (.patterns/skill-script-shell-shape.md, ADR 0232).verify-step3z-precondition.shdrives the script over six head states against stubbedgh/pipeline-cliand asserts, from the recorded call log rather than the script's own narration, that no refusal touched the PR. It generates its own mutant — the precondition block deleted — and requires that mutant to nudge the green head, so an assertion that stops having teeth reds instead of passing quietly. Wired into theskillsCI job besideverify-chain-resolves.sh.Verification
verify-step3z-precondition.sh— PASS, 6 states + the mutant.verify-chain-resolves.sh— PASS (the chain still runs as written).shellcheckclean on both scripts;trap-status-guard,cli-invocation-guard,gh-phoenix lint-skills,adoption-lint,leak-guardclean;actionlintclean onci.yml.Deviations
Class: narrowed the suggested fix-shape.
CONTEXTS/NRUNSas arguments, or by re-deriving them at the head."NWF, alongside them.NWFis part of the same branch-3 predicate — without it a repo that runs no Actions hasNRUNS == 0by definition and would be nudged, which is the fail-open direction branch 3 exists to close.Class: scope judgment on where the derivation lives.
pipeline-cliverb's answer..contextsfromchecks read, but readNWF/NRUNSover REST inline, because no verb exposes a head's workflow-run count (checks readreads check-runs,run-evidence readreads the bundle).step3-empty-checkset-probe.sh, which makes the same two reads; folding this glue into verbs is the phase-2 rewrite the issue puts out of scope (Consolidate ad-hoc gh/jq/git glue in pipeline skills into deterministic pipeline-cli subcommands #1929).Class: a stop-path behaviour the ACs did not specify.
disarm_intent refusebefore it prints, like the two existing exits.--autoparked.