Skip to content

fix(ship-it): Step 3z asserts its own zero-runs precondition before it nudges (#4830) - #4853

Merged
usirin merged 2 commits into
mainfrom
usirin/step3z-precondition-4830
Aug 8, 2026
Merged

fix(ship-it): Step 3z asserts its own zero-runs precondition before it nudges (#4830)#4853
usirin merged 2 commits into
mainfrom
usirin/step3z-precondition-4830

Conversation

@usirin

@usirin usirin commented Aug 3, 2026

Copy link
Copy Markdown
Member

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.sh asserts its own precondition. It re-derives the three numbers at the head — CONTEXTS relayed from pipeline-cli checks read (ADR 0228: relay the one head-CI rollup reader, never derive a second rollup here), plus the actions/workflows and actions/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.
  • A third terminal outcome, and it mutates nothing. refused (not the dropped-trigger state: CONTEXTS=<n> NWF=<n> NRUNS=<n>) — no nudge — no state=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 …=unreadable and 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.
  • Everything else is preserved. Both existing terminal outcomes, the once-per-head exhaustion bound (still leaving its durable ship-it: a shipper that parks on the CI-settle poll can silently die without enqueuing — PR left green-but-not-queued, no outcome comment #1928 comment), guard 6's disarm_intent refuse on every exit, and the dual-mode / no-EXIT-trap shell shape (.patterns/skill-script-shell-shape.md, ADR 0232).
  • verify-step3z-precondition.sh drives the script over six head states against stubbed gh / pipeline-cli and 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 the skills CI job beside verify-chain-resolves.sh.

Verification

  • verify-step3z-precondition.sh — PASS, 6 states + the mutant.
  • verify-chain-resolves.sh — PASS (the chain still runs as written).
  • shellcheck clean on both scripts; trap-status-guard, cli-invocation-guard, gh-phoenix lint-skills, adoption-lint, leak-guard clean; actionlint clean on ci.yml.

Deviations

Class: narrowed the suggested fix-shape.

  • Said: the issue offered "either by taking CONTEXTS / NRUNS as arguments, or by re-deriving them at the head."
  • Did: re-derive at the head only, and asserted a third number, NWF, alongside them.
  • Why: arguments would keep the trust in the caller (and a test asserting a refusal the caller never supplies would prove nothing). NWF is part of the same branch-3 predicate — without it a repo that runs no Actions has NRUNS == 0 by definition and would be nudged, which is the fail-open direction branch 3 exists to close.
  • Disposition: no action needed.

Class: scope judgment on where the derivation lives.

  • Said: ADR 0228 bounds these scripts to relaying a pipeline-cli verb's answer.
  • Did: relayed .contexts from checks read, but read NWF / NRUNS over REST inline, because no verb exposes a head's workflow-run count (checks read reads check-runs, run-evidence read reads the bundle).
  • Why: this mirrors 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).
  • Disposition: for the reviewer to judge.

Class: a stop-path behaviour the ACs did not specify.

  • Said: "refuses without mutating the PR."
  • Did: the refusal still runs disarm_intent refuse before it prints, like the two existing exits.
  • Why: guard 6 (ADR 0198) requires every non-enqueuing stop to leave no armed merge intent; disarming is the fail-safe direction and skipping it here would let a refused run leave --auto parked.
  • Disposition: no action needed.

usirin added 2 commits August 3, 2026 01:20
…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
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🚀 Preview deployed

  • web — Stage pr-4853 torn down.

@usirin

usirin commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

review-code: advisory — blocking-set PR (§CP — approval-gated)

PR #4853 is §CP — cp-classify classify re-derived it against the live CONTROL_PLANE_RE from origin/main and returned control-plane [path-match].github/workflows/ci.yml matches. BLOCKING (human merge). My verdict is advisory only: it does not authorize a merge. Under the §CP hard gate (ADR 0135), a @kamp-us/control-plane member approves this at its current head and ship-it then enqueues it (ADR 0048 single merge authority).

Reviewed-head: @ 5a2be93

Required namespaces for this diff (pipeline-cli class-probe classify --namespaces, 4 changed files → has-code, has-skills): review-code, review-skill — both emitted this pass, each as its own comment. No apps/web/src/** path, so review-design is not required and is not emitted.

Read the PR head (§HEAD): every file under review was sourced from 5a2be93d through a per-run ref and an isolated throwaway tree cut from it, never from the launched checkout's working copy; nothing in the shared checkout was mutated (§RO).

Verified PR #4853 against the acceptance criteria of #4830, one at a time — all pass:

  • [PASS] AC1 — the script asserts the precondition itself before any PATCH. claude-plugins/kampus-pipeline/skills/ship-it/scripts/step3z-dropped-trigger.sh now re-derives CONTEXTS / NWF / NRUNS at the head and computes DROPPED before the exhaustion-bound reads and before either PATCH state=… call. I falsified the ordering by running the script under stubs on the docs(decisions): record the release mechanism — release-please manifest mode (#4799) #4816 shape (CONTEXTS=45 NWF=45 NRUNS=33): the recorded call log is checks read --sha …, the two actions/* reads, merge-intent disarm … --site refuse — and nothing else. Zero PATCH, zero comments.
  • [PASS] AC1a — CONTEXTS is RELAYED, not a second rollup (ADR 0228). The only rollup read is "$CHECKS_CLI" checks read --sha "$HEAD_SHA" | jq -r '.contexts? // empty'. I searched the file for any independent check-runs / .conclusion derivation: there is none. I also confirmed the relay resolves against a live head — checks read --sha 5a2be93d emits its JSON line first, so the expression captures 44 cleanly even though the verb also prints a human line and exits non-zero on a non-green rollup. NWF / NRUNS remain raw REST reads; per ADR 0228 that is not a DERIVE (the ADR explicitly names "reading check-runs feeds it" as the reason the earlier feeds-a-decision test was rejected), and they are byte-identical to the reads step3-empty-checkset-probe.sh already makes. Judged as the author disclosed it — acceptable, and Consolidate ad-hoc gh/jq/git glue in pipeline skills into deterministic pipeline-cli subcommands #1929 owns the verb migration.
  • [PASS] AC2 — a failed precondition refuses without mutating the PR. if [ "$DROPPED" -ne 1 ] prints and exit 0 before the exhaustion reads; the branch body contains no gh api at all, deliberately (a comment here would repeat docs(decisions): record the release mechanism — release-please manifest mode (#4799) #4816's false claim). Proven twice: by the stub call-log run above, and by mutating the fix to post a comment on the refusal path — the harness went red on all four refusal cases with REFUSED but still mutated the PR, so the no-mutate assertion is not a proxy for the terminal string.
  • [PASS] AC3 — a distinct third terminal line. refused (not the dropped-trigger state: CONTEXTS=<n> NWF=<n> NRUNS=<n>) — no nudge, lexically disjoint from nudged (close→reopen) … and unverified (no runs fired …). Observed verbatim on a live stub run.
  • [PASS] AC4 — fail-closed on an unreadable subject; an unknown is never zero. This is the crux, so I attacked it directly. The normalizer is case "$X" in ''|*[!0-9]*) X=unreadable ;; esac — stricter than the sibling probe's bare -z test, and it rejects whitespace, multi-line and non-numeric captures. I tried to construct a failed read that resolves to a plausible number: the obvious vector is jq's null | length == 0, which would turn a 404 body into NRUNS=0. It does not fire — gh api --jq does not apply the filter to a non-2xx response; I confirmed empirically that a 404 on actions/runs puts the raw {"message":"Not Found",…} body on stdout (exit 1), which the *[!0-9]* arm catches. A missing gh, a missing jq, an unresolved CLI shim (kp_pcliCHECKS_CLI=""), and a network failure all yield an empty capture ⇒ unreadable ⇒ refuse. Then I broke the fix myself: changing the normalizer to NRUNS=0 makes the script nudge an unreadable head, and the harness reds (stdout missing NRUNS=unreadable … got: nudged (close→reopen)).
  • [PASS] AC5 — the exhaustion bound and both existing outcomes preserved; shell shape preserved. The HEAD_PUSHED / NUDGES / >= 1 block is byte-identical in the diff. disarm_intent refuse moved earlier (from after the exhaustion reads to before the new branch) — it still runs on every exit path, which I verified from the call log (merge-intent disarm --pr 1 --repo owner/repo --site refuse is recorded on the refusal path), so guard 6 / ADR 0198 holds on the new terminal too. Shell shape: set -uo pipefail guarded to executed mode, no -e, no EXIT trap in either file; INTENT_UNCLEARED is safe under set -u because disarm-intent.sh seeds it "${INTENT_UNCLEARED:-0}" at source time. No arrays are introduced, so the defaulted-expansion and emptiness-before-expansion rules are vacuous here. pipeline-cli trap-status-guard check over both scripts + ship-it/SKILL.md: clean — no runnable shell unit pairs errexit with an EXIT trap (3 files, 29 fences, 2 implicit units). shellcheck -s bash clean on both, exit 0.
  • [PASS] AC6 — SKILL.md documents the third outcome. claude-plugins/kampus-pipeline/skills/ship-it/SKILL.md updates all three places the state machine is stated: the Step 3z invocation fence's stdout contract, the "all three Step 3z outcomes" paragraph, and the ship-it outcome vocabulary near the end. I cross-read Step 3's branch list: branch 3 is reachable only via branch 1b ($CONTEXTS == 0), so the script's three legs — CONTEXTS == 0, NWF >= 1, NRUNS == 0 — characterize exactly the branch that dispatches Step 3z, and introduce no false refusal relative to it. The NWF >= 1 leg's reasoning holds: a repo running no Actions has NRUNS == 0 by definition, and dropping the leg makes the script nudge such a repo — I mutated it out and the harness reds on repo runs no Actions.
  • [PASS] CI wiring is executed, not merely declared. .github/workflows/ci.yml adds - run: bash .claude/skills/ship-it/scripts/verify-step3z-precondition.sh to the unconditional skills job. Confirmed it actually ran at this head: job validate skill frontmatter (91633720780), step 11, conclusion success. The .claude/skills symlink resolves to claude-plugins/kampus-pipeline/skills, and the harness is present at the target.
  • [PASS] deviation-disclosure (§DEV). ## Deviations is present with three disclosed classes, each with Said / Did / Why / Disposition — including the ADR-0228 scope call explicitly routed "for the reviewer to judge". Nothing this gate detected is undisclosed. (Stated as nothing undisclosed that this gate could see, not as no deviations exist.)
  • [N/A] flag-gating (Step 3b) — ship-it Step 3z close-reopens a live PR without asserting its own zero-runs precondition #4830 carries no **Containment:** marker and ships no user-facing surface.
  • [N/A] glossary-freshness (Step 3c) — no new feature folder, package, or public export.

Run-evidence bundle: PRESENT for head 5a2be93d — producer run 30797315194, artifact 8849500699, manifest.commit == head, schemaVersion 1; checks 2/2 pass; tests 2420/2420 passed, 0 failed, 0 skipped.

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:

mutation expected harness result
NRUNS=unreadableNRUNS=0 (an unknown resolved to a plausible zero) red redNRUNS unreadable … got: nudged (close→reopen)
delete the NWF leg red redrepo runs no Actions … got: nudged (close→reopen)
refusal path also posts a comment red redREFUSED but still mutated the PR, on all four refusal cases

The harness's own mutant generator was checked for the vacuity it guards against: its sed range terminates on the standalone fi, not on the one-line inner if … fi, so it deletes the whole precondition block — and the cmp -s arm reds if it ever deletes nothing.

Non-blocking observations (no action required, not gating):

  1. checks read takes no --repo, so the CONTEXTS relay resolves the repo from CLAUDE_PIPELINE_REPO / GITHUB_REPOSITORY / the ambient repo rather than from the script's own $REPO argument. In ship-it these are the same value by construction, and any mismatch would surface as a non-zero CONTEXTS ⇒ refuse (the safe direction). Worth a --repo flag whenever checks read next changes.
  2. The harness asserts the call log for PATCH / comments but does not assert that merge-intent disarm --site refuse fired on the new refusal path. I verified that by hand this pass; pinning it in the harness would make guard 6 regression-proof on the new terminal too.
  3. If kp_pcli cannot resolve the shim, CONTEXTS is permanently unreadable and Step 3z can never nudge. That is the correct fail direction, but it turns a resolvable environment fault into a silently disabled remedy — the refusal line does name it (CONTEXTS=unreadable), which is enough to diagnose.

All criteria pass. review-code does not merge — under §CP, a @kamp-us/control-plane approval at this head plus ship-it is the path; merging will auto-close #4830 via Fixes #4830.

Verdict-written: 2026-08-03T08:41:37Z

@usirin

usirin commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

review-skill: advisory — blocking-set PR (§CP — approval-gated)

PR #4853 edits the agent control plane: cp-classify classify, re-derived against the live CONTROL_PLANE_RE on origin/main, returned control-plane [path-match].github/workflows/ci.yml matches. BLOCKING (human merge), and the diff also touches the gate-critical skill ship-it/** (ADR 0053/0065/0073). My verdict is advisory only: it does not authorize a merge. Under ADR 0135 a @kamp-us/control-plane member approves at this head and ship-it enqueues (ADR 0048 single merge authority).

Reviewed-head: @ 5a2be93

Required namespaces for this diff (pipeline-cli class-probe classify --namespaces, 4 changed files → has-code, has-skills): review-code, review-skill — both emitted this pass, each as its own comment.

Skill text was read from the PR head 5a2be93d through a per-run ref; my own operating config came from the trusted base, never the head (ADR 0052/0067).

Verified against #4830's acceptance criteria + the skill-rigor checklist — all checks pass:

Acceptance criteria

  • [PASS] AC1 — step3z-dropped-trigger.sh re-derives CONTEXTS / NWF / NRUNS at the head and evaluates DROPPED before either PATCH state=…. Confirmed by executing the script under stubbed gh / pipeline-cli on the docs(decisions): record the release mechanism — release-please manifest mode (#4799) #4816 head shape: the call log holds only the three reads and merge-intent disarm … --site refuse.
  • [PASS] AC2 — the refusal branch contains no gh api at all; no close→reopen, no comment. I mutated the branch to post a comment and the harness went red on all four refusal cases (REFUSED but still mutated the PR), so the assertion is real and not a restatement of the terminal string.
  • [PASS] AC3 — third terminal line refused (not the dropped-trigger state: CONTEXTS=<n> NWF=<n> NRUNS=<n>) — no nudge, disjoint from the two existing terminal words.
  • [PASS] AC4 — every lookup normalizes through case "$X" in ''|*[!0-9]*) X=unreadable ;; esac, and unreadable never satisfies the 0 arm. I tried to defeat it (jq's null | length == 0 turning a 404 into a plausible 0) and could not: gh api --jq does not apply the filter to a non-2xx body, so the raw {"message":"Not Found",…} reaches the non-digit arm. Mutating NRUNS=unreadableNRUNS=0 makes the script nudge an unreadable head and the harness reds.
  • [PASS] AC5 — the once-per-head exhaustion block is byte-identical; both prior terminal outcomes still assert green in the harness; disarm_intent refuse moved earlier but still precedes every exit (verified from the recorded call log on the new refusal path). Shell shape preserved: set -uo pipefail in executed mode only, no -e, no EXIT trap; pipeline-cli trap-status-guard check clean over both scripts and ship-it/SKILL.md; shellcheck -s bash clean.
  • [PASS] AC6 — ship-it/SKILL.md Step 3z documents the refusal as a third terminal outcome in all three places the state machine is stated (the invocation fence's stdout contract, the "all three Step 3z outcomes" paragraph, and the outcome-vocabulary section).

Skill-rigor checklist

  • [PASS] Behavioral correctness. I traced Step 3z as an agent would execute it. The prose now tells the agent that a refusal means the dispatch was wrong, not the PR, and points back at Step 3's branch list — a followable recovery, not a dead end. The script's three legs match the branch that dispatches it: branch 3 is reachable only through branch 1b ($CONTEXTS == 0), so CONTEXTS == 0 + NWF >= 1 + NRUNS == 0 is exactly branch 3's predicate, and no legitimate Step-3z dispatch is newly refused. All three outcomes remain exit 0 successful declines, so the documented "read the terminal word, not the status" contract still holds and no caller that branches on status is broken. The one behavior change that is not a refusal — disarm_intent refuse running earlier — strictly widens guard-6 coverage.
  • [PASS] Trigger / description quality. ship-it's frontmatter description is untouched; no new skill and no new trigger surface is introduced. The added file is a script under an existing skill's scripts/, not a routable skill, so there is nothing new to over- or under-trigger.
  • [PASS] Cross-skill conflict / shadowing. The only consumer of Step 3z's stdout is ship-it/SKILL.md itself, and both places it names the outcome set were updated in the same diff — no seam left half-changed. I searched the plugin and package trees for other references to step3z / the terminal strings: the only other hit is the gh-phoenix lint allowlist entry, which is unaffected. The sibling step3-empty-checkset-probe.sh reads the same two REST endpoints in the opposite fail direction by design (it substitutes toward "do not nudge"; this script keeps the failure visible so it can refuse on it) — both fail toward not-nudging, so the pair is consistent, not contradictory.
  • [PASS] Gate-invariant preservation. This diff strengthens a gate and weakens none. ship-it's §CP refusal, the latest-current-head-PASS-per-namespace gate, the SHA-staleness refusal, the run-evidence bundle assertion and the ACL author-gate are all untouched by the diff. Within Step 3z: the once-per-head nudge bound is byte-identical, the ship-it: a shipper that parks on the CI-settle poll can silently die without enqueuing — PR left green-but-not-queued, no outcome comment #1928 durable-outcome rule is intact on the exhausted path, and guard 6 / ADR 0198 now fires on three exits instead of two. The change adds a fail-closed precondition to the single destructive server-side action in the merge authority — the safe direction in every branch, including on an unreadable read. No matcher, marker shape, §CP set, or verdict contract is touched.
  • [PASS] deviation-disclosure (§DEV). ## Deviations present, three classes, each with Said / Did / Why / Disposition — including the ADR-0228 scope call routed explicitly to the reviewer. I judged it: relaying .contexts from checks read is the sanctioned RELAY branch, and the NWF/NRUNS REST reads are not a DERIVE under 0228 (the ADR itself names "reading check-runs" as why the feeds-a-decision test was rejected). Nothing this gate detected is undisclosed.
  • [PASS] CI wiring is executed, not merely declared. The harness runs as step 11 of the unconditional skills job (validate skill frontmatter, job 91633720780) at this head, conclusion success.

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:

mutation harness result
NRUNS=unreadableNRUNS=0 redNRUNS unreadable … got: nudged (close→reopen)
delete the NWF >= 1 leg redrepo runs no Actions … got: nudged (close→reopen)
refusal path also posts a comment redREFUSED but still mutated the PR, all four refusal cases

Its self-generated mutant is also non-vacuous: the sed range ends at the standalone fi, not the one-line inner if … fi, so it removes the whole precondition block, and the cmp -s arm reds if the generator ever deletes nothing.

Non-blocking note (not gating): the harness asserts the recorded call log for PATCH / comments but does not assert that merge-intent disarm --site refuse fired on the new refusal path. I verified that by hand this pass; pinning it would make guard 6 regression-proof on the new terminal too.

Every acceptance criterion and every rigor check passes. review-skill does not merge — under §CP this is approval-gated; ship-it enqueues after a control-plane approval at this head.

Verdict-written: 2026-08-03T08:43:15Z

@usirin
usirin requested a review from cansirin August 3, 2026 08:44
@usirin
usirin added this pull request to the merge queue Aug 3, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 3, 2026
@usirin
usirin added this pull request to the merge queue Aug 3, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 3, 2026
@usirin

usirin commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

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; merged_at: null, no paired merged timeline event). This is a combined-batch CI failure, not a textual conflict (ADR 0132; GitHub "Managing a merge queue").

Batch: gh-readonly-queue/main/pr-4853-36d96c5aae2c6c1e58a566b41dd900830e8100ee — CI run 30800421641, job packages unit tests.

Failing suite/test — new, and different from the first ejection's cause:

FAIL packages/pipeline-cli/src/tools/scratchpad/command.concurrency.test.ts
  > scratchpad open — concurrent claims on one namespace
  > admits exactly one of eight concurrent opens; every other exits 4 (ForeignNamespace)
AssertionError: a loser must be refused as ForeignNamespace, not 0 or 6: expected 6 to equal 4
  - Expected: 4
  + Received: 6
  at packages/pipeline-cli/src/tools/scratchpad/command.concurrency.test.ts:70:11

The first ejection's cause (#4847, packages/fabrika-cli/src/excess-operand.cli.test.ts timing out at 30s) is fixed and confirmed gone: in this batch that suite passed — ✓ src/excess-operand.cli.test.ts (5 tests) 14075ms. PR #4857's fix took.

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 5a2be93d — the failure only appears in the batch build against the newer base.

Routing back to repair/re-queue — this is NOT a shipped state. ship-it did not re-enqueue and left no armed merge intent.

@usirin
usirin added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 5055306 Aug 8, 2026
46 checks passed
@usirin
usirin deleted the usirin/step3z-precondition-4830 branch August 8, 2026 21:31
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.

ship-it Step 3z close-reopens a live PR without asserting its own zero-runs precondition

3 participants