fix(pr-merge): refuse to merge a PR whose forge checks are red or unreadable - #2299
Open
Nikita-Guzenko wants to merge 1 commit into
Open
fix(pr-merge): refuse to merge a PR whose forge checks are red or unreadable#2299Nikita-Guzenko wants to merge 1 commit into
Nikita-Guzenko wants to merge 1 commit into
Conversation
…eadable bin/fm-pr-merge.sh called gh-axi pr merge without ever reading the forge's check state, so "never merge a red PR" lived only in AGENTS.md and depended on session attention. On 2026-08-10 that gap shipped a broken commit: the merge-queue run for nguzen/aln pull request 182 failed at 17:54 UTC, the pull request was merged at 17:59 UTC, and the defect in it blocked every production deploy until 2026-08-12. The merge path now reads the verdict itself through the new bin/fm-pr-checks-lib.sh and refuses before recording any state: * failing at least one check the forge reports as failed * unreadable the state could not be established - never treated as a pass Two sources are read, because either can be red alone: the pull request head's status check rollup, and the newest merge-queue attempt, whose checks run again on a combined commit published as gh-readonly-queue/<base>/pr-<n>-<sha>. Only the newest queue attempt is judged, so a superseded red attempt cannot block a re-queued green one. `gh pr checks` is deliberately unused: it exits non-zero both when a check failed and when one is still running, so its exit status cannot classify a result. --allow-failing-checks is the single deliberate override, and it announces itself; AGENTS.md records that using it is a red merge and needs the same explicit captain instruction any red merge needs. Verified against the live incident: the library classifies nguzen/aln#182 as failing (branch and merge-queue) and kunchenguid#183 as green. Tests: tests/fm-pr-merge.test.sh gains the red-branch, red-merge-queue-with- green-branch, superseded-attempt, unreadable, wrong-payload, override and unknown-flag cases; both guards were mutation-checked (blinding the queue read fails only the queue case, accepting an unreadable verdict fails only the unreadable case).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Make bin/fm-pr-merge.sh refuse to merge a pull request whose forge reports a failing check, so that "never merge a red PR" is enforced by the one merge path instead of by session attention.
Why: on 2026-08-10 the merge-queue run for nguzen/aln pull request 182 failed at 17:54 UTC and the pull request was merged at 17:59 UTC by the same account; the defect in that commit then blocked every production deploy until 2026-08-12, and the product owner asked about missing changes twice. bin/fm-pr-merge.sh read no check state at all, and the no-mistakes CI monitor on this box cannot read check results either (its gh pr checks call fails), so two blind spots combined into a two-day outage.
Requirements the user stated, all deliberate:
Named trap, verified 2026-08-12: gh pr checks exits non-zero both when something failed and when something is still running, so classification by exit status is impossible - only output values can classify. That is why this change does not use gh pr checks at all.
Decisions made while implementing, so they are not mistakes:
Verification already performed: the library classifies the live incident correctly - nguzen/aln#182 as failing (branch check "e2e visual QA (Playwright)" plus merge-queue check CI) and #183 as green. tests/fm-pr-merge.test.sh gained seven cases (red branch, red merge queue with a green branch, superseded queue attempt, unreadable rollup read, unreadable queue read, payload for another pull request, override flag, unknown own flag) and passes 17/17; tests/fm-pr-check-security.test.sh passes 36/36 after its gh mock was taught the two new reads; bin/fm-lint.sh, bin/fm-doc-audience-check.sh and bin/fm-test-run.sh --check-coverage are clean. Both guards were mutation-checked: blinding the queue read fails only the queue case, and accepting an unreadable verdict fails only the unreadable case.
One factual correction to the original report, deliberately reflected in the code and the commit message: pull request 182 was red on BOTH sources, not only the queue - its own branch run failed the same visual-QA check at 17:56:50. The branch-green/queue-red split is therefore covered by a synthetic test fixture rather than by that live pull request, and both sources are read because either can be red alone.
What Changed
bin/fm-pr-checks-lib.sh, a sourced GitHub-only library that classifies a PR's forge check state from two independent sources — the PR head'sstatusCheckRollup(viagh pr view) and the newest merge-queue attempt's Actions runs (viagh api ...runs?event=merge_group) — using jq value inspection rather thangh pr checksexit codes, which cannot distinguish failing from still-running.bin/fm-pr-merge.shon that verdict before it runsfm-pr-check.sh: a failing or unreadable state refuses the merge with a non-zero exit and prints exactly which checks are red, whilependingis permitted with a visible note. Branch-sideCANCELLED/STALEcount as failures; queue-side cancelled/stale count as inconclusive since the queue re-runs them. Only the newest queue attempt (newest run per workflow) is judged so a superseded red attempt cannot block a re-queued green PR.--allow-failing-checksoverride (consumed as an own flag before--, announced on stderr) that skips the read entirely for deliberate red/infrastructural merges; an unknown own flag is now a usage error (exit 2) rather than being forwarded to gh-axi.tests/fm-pr-merge.test.sh(17/17) andtests/fm-pr-check-security.test.shwith fixtures for red branch, branch-green/queue-red, superseded queue attempt, unreadable rollup/queue reads, foreign-PR payload, the override flag, and the unknown-flag case; updatedAGENTS.md,docs/architecture.md, anddocs/scripts.mdto record that--allow-failing-checksis itself a red merge.Risk Assessment
✅ Low: Well-bounded, additive change with a new sourced classifier and a pre-state merge gate; every required intent criterion is met, error paths fail closed to refusal, and it is covered by mutation-checked tests.
Testing
Ran the two changed suites and built an end-to-end demo. The primary target, tests/fm-pr-merge.test.sh, passes 17/17 and directly exercises each intent requirement including the merge-queue-red / branch-green split and the unreadable-is-refusal rule. tests/fm-pr-check-security.test.sh completed with exit 0 and no failures, confirming the new forge reads don't regress the existing recording/derivation and teardown-safety behavior. As the reviewer-visible artifact I drove the real fm-pr-merge.sh with a stubbed forge and captured the actual stderr for all six verdicts (red branch, branch-green/queue-red, unreadable, --allow-failing-checks, all-green, unknown flag), showing refusals exit 1 before arming the check poll while green/override proceed to the gh-axi merge call. This is a CLI/operator-facing change with no rendered UI surface, so the CLI transcript is the appropriate end-user evidence. Worktree left clean; evidence written only under /tmp.
Evidence: End-to-end merge-gate CLI transcript (6 verdicts against the real script)
=== 1. RED branch check === error: refusing to merge .../pull/182: the forge reports failing checks branch check: e2e visual QA (Playwright) (FAILURE) [exit 1] === 2. Branch GREEN but MERGE-QUEUE RED === error: refusing to merge ...: the forge reports failing checks merge-queue check: CI (FAILURE) (merge-queue attempt read: gh-readonly-queue/main/pr-182-abc) [exit 1] === 3. UNREADABLE (queue read fails) === error: refusing to merge ...: the forge's check state is unreadable (the merge-queue check runs of nguzen/aln could not be listed) [exit 1] === 4. --allow-failing-checks === warning: merging ... without reading the forge's check verdict (--allow-failing-checks) >> gh-axi pr merge 182 --repo nguzen/aln --squash (MERGE EXECUTED) [exit 0] === 5. All GREEN === >> gh-axi pr merge 182 --repo nguzen/aln --squash (MERGE EXECUTED) [exit 0] === 6. Unknown own flag === error: unknown merge flag --force (own flags: --allow-failing-checks; pass gh-axi flags after --) [exit 2]Evidence: Demo driver script (reusable)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-pr-merge.test.sh— 17/17 pass (red branch, red merge queue w/ green branch, superseded queue attempt, unreadable rollup, unreadable queue, foreign-PR payload, --allow-failing-checks, unknown own flag)bash tests/fm-pr-check-security.test.sh— exit 0, no failures (regression: gh mock taught the two new reads with green defaults)End-to-end CLI transcript driving the real bin/fm-pr-merge.sh against a stubbed gh/gh-axi for all six operator-visible verdicts: /tmp/no-mistakes-evidence/01KZWWDSMSBGGT792YQ5ENAAWV/demo.sh✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.