Skip to content

fix(bin): refuse a merge when a PR's CI event may have been dropped - #12

Merged
rub-a-dub-dub merged 15 commits into
mainfrom
fm/firstmate-detect-dropped-ci-event-v2
Sep 20, 2026
Merged

rub-a-dub-dub merged 15 commits into
mainfrom
fm/firstmate-detect-dropped-ci-event-v2

Conversation

@rub-a-dub-dub

Copy link
Copy Markdown
Owner

Intent

Firstmate nearly merged PR 34 in rub-a-dub-dub/personal-agent-skills on the ground that it had "no CI checks configured" -- that PR was actually red, with a stale generated file, one merge away from landing; the string looked exactly like a green board. Make firstmate unable to treat an absent check as a passing one, per the following detection rule: (1) Repo-level, computed once rather than per-PR: does any workflow declare a pull_request (or pull_request_target) trigger? If none does, the repo genuinely has no PR CI, absence is expected, and nothing further applies. (2) Otherwise, count runs at the PR's current head SHA filtered to event == "pull_request": one or more runs -> read the conclusion as today, the ordinary case; zero runs and the head commit is younger than a five-minute grace window -> "not arrived yet", not actionable, re-check; zero runs and the head commit is older than that window -> suspected dropped event, never green, do not merge. The critical trap: a workflow_dispatch diagnostic run leaves a check-run on the SHA even though it is not a pull_request-triggered run and does not attach to the PR -- filter strictly on event type, never on whether any run exists at all, or a manual diagnostic dispatch will be misread as proof the checks arrived. This rule may only ever make the merge gate stricter: a repo with no PR CI must keep merging exactly as it does today, and a green PR must not become unmergeable because of a latency edge -- both of those must be tested explicitly.

What Changed

  • bin/fm-pr-merge.sh gains a two-step dropped-CI-event gate that runs independently of the check rollup: github_repo_has_pr_ci_workflow asks once per merge attempt whether any workflow file declares a pull_request trigger (via the github_workflow_declares_pull_request text heuristic, which scans only the on: block at its immediate child indentation, handles the "on":/'on': spellings and zero-indented sequences, and deliberately does not count pull_request_target), and only a confirmed trigger arms the second step.
  • github_check_dropped_ci_event then counts Actions runs at the live head through the API's own ?event=pull_request filter rather than by whether any run exists at the SHA, so a workflow_dispatch diagnostic run no longer reads as proof the checks arrived. A count of zero refuses unconditionally; the head commit's own committer date against the new fixed FM_PR_MERGE_CI_GRACE_SECS=300 only selects between the "not arrived yet, re-check" and "suspected dropped event" wording, and an unreadable date lands on the latter. Reads that fail before an absence is confirmed — an unreadable workflow listing or run count — leave the gate disarmed and print a note on stderr.
  • tests/fm-pr-merge.test.sh adds 12 cases covering both directions: repos with no workflows directory, push-only, pull_request_target-only, commented-out, and nested-workflow_dispatch-input triggers keep merging unchanged, as do a green PR with a run present and an unreadable workflow listing; single-quoted on: and zero-indented sequence triggers arm the gate; and in-grace, past-grace, and unreadable-commit-date zero-run heads each refuse with their own message. docs/architecture.md documents the rule and the branches:/paths:-filtered exemption it knowingly costs.

Risk Assessment

✅ Low: The change is purely additive, matches the design the user explicitly authorized across five decision rounds, and I verified its core heuristic and every prior fix claim empirically rather than by inspection alone; the only known unwaivable-refusal classes are recorded, accepted user decisions tracked under a separate exemption ticket, and the one residual gap I found fails in the conservative direction.

Testing

Ran the change's 12 new cases in tests/fm-pr-merge.test.sh (all pass) and, because passing unit tests alone do not show the near-miss closing, drove bin/fm-pr-merge.sh directly against a mocked GitHub in the exact PR-34 state and captured the CLI transcripts: the base-commit script reports "every required check green" and issues gh pr merge, while this branch refuses with exit 1, issues no merge, and still refuses under --allow-red - with the gh call log showing the run count is read through ?event=pull_request so the workflow_dispatch diagnostic run at the same SHA never counts. Five stricter-only scenarios were run against both script versions and behave identically before and after (no PR CI, a green PR with its pull_request run present, a pull_request_target-only repo, an unreadable workflow listing), with only the 3-minute-old head newly refused and worded as re-check-shortly rather than a dropped event. Two setup problems were handled: a missing tasks-axi (installed into a temp prefix on PATH, then removed) and a host-load flake in shared pre-merge code that also hits the base script, which blocked a complete run of the 130-case file locally and is reported as a finding.

Evidence: PR-34 near miss: base commit merges it, this branch refuses it (CLI transcript)

Source: PR-34 near miss: base commit merges it, this branch refuses it (CLI transcript)

BEFORE - base commit 126eaec bin/fm-pr-merge.sh $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34
verified: https://github.com/example/repo/pull/34 is open and mergeable, with every required check green at head 5a5a...5a5a verified: https://github.com/example/repo/pull/34 is merged (state=MERGED, merged=true, isInMergeQueue=false) exit status: 0 --- VERDICT: MERGED an unverified head - the near miss AFTER - this branch, same mocked GitHub state $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34
error: refusing to merge https://github.com/example/repo/pull/34
- no pull_request-triggered check has reported for head 5a5a...5a5a, and its commit is already past the delivery grace window: wait and retry this merge first, because a run still on its way looks identical here once the commit has aged out of the window, and treat it as a suspected dropped CI event only if a retry still finds none. Neither is ever treated as green exit status: 1 --- gh calls this run made: api repos/example/repo/contents/.github/workflows --jq .[] | select(.type == "file") | .name api repos/example/repo/actions/runs?head_sha=5a5a...5a5a&event=pull_request --jq .total_count api repos/example/repo/commits/5a5a...5a5a --jq .commit.committer.date --- VERDICT: REFUSED - no gh pr merge was ever issued AFTER - retried with --allow-red ci --- VERDICT: REFUSED - --allow-red cannot waive an absent check


========================================================================
SCENARIO: the PR-34 near miss, reproduced against a mocked GitHub
========================================================================
Pull request : https://github.com/example/repo/pull/34
Head commit  : 5a5a...5a5a, committed 2025-12-31T23:00:00Z (60 minutes before "now")
gh pr view   : statusCheckRollup = []        <- reads as "no CI checks configured"
Workflows    : .github/workflows/ci.yml declares  on:\n  pull_request:\n  push:
Actions runs at that SHA:
  - unfiltered (?head_sha=)                     -> total_count 1  (a manual
    workflow_dispatch diagnostic run, never attached to the PR)
  - filtered   (?head_sha=&event=pull_request)  -> total_count 0  (nothing the
    pull_request event ever delivered)

========================================================================
BEFORE - base commit 126eaec bin/fm-pr-merge.sh
========================================================================
$ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34
verified: https://github.com/example/repo/pull/34 is open and mergeable, with every required check green at head 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/34 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0
--- gh calls this run made:
pr view https://github.com/example/repo/pull/34 --json headRefOid -q .headRefOid
pr view https://github.com/example/repo/pull/34 --json state,isDraft,mergeable,mergeStateStatus,headRefOid,baseRefName,statusCheckRollup
pr merge 34 --repo example/repo --match-head-commit 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a --squash
api graphql -f query=query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){state merged isInMergeQueue baseRefName}}} -F owner=example -F repo=repo -F number=34 --jq .data.repository.pullRequest | "state=" + (.state // ""), "merged=" + (.merged | tostring), "queued=" + (.isInMergeQueue | tostring), "base=" + (.baseRefName // "")
--- VERDICT: MERGED an unverified head - the near miss

========================================================================
AFTER - this branch's bin/fm-pr-merge.sh, same mocked GitHub state
========================================================================
$ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34
error: refusing to merge https://github.com/example/repo/pull/34
  - no pull_request-triggered check has reported for head 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a, and its commit is already past the delivery grace window: wait and retry this merge first, because a run still on its way looks identical here once the commit has aged out of the window, and treat it as a suspected dropped CI event only if a retry still finds none. Neither is ever treated as green
armed: state/task-x1.check.sh
exit status: 1
--- gh calls this run made:
pr view https://github.com/example/repo/pull/34 --json headRefOid -q .headRefOid
pr view https://github.com/example/repo/pull/34 --json state,isDraft,mergeable,mergeStateStatus,headRefOid,baseRefName,statusCheckRollup
api repos/example/repo/contents/.github/workflows --jq .[] | select(.type == "file") | .name
api repos/example/repo/contents/.github/workflows/ci.yml --jq .content // ""
api repos/example/repo/actions/runs?head_sha=5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a&event=pull_request --jq .total_count
api repos/example/repo/commits/5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a --jq .commit.committer.date
--- VERDICT: REFUSED - no gh pr merge was ever issued

========================================================================
AFTER - the same head, retried with --allow-red ci
========================================================================
$ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34 --allow-red ci
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
error: refusing to merge https://github.com/example/repo/pull/34
  - no pull_request-triggered check has reported for head 5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a, and its commit is already past the delivery grace window: wait and retry this merge first, because a run still on its way looks identical here once the commit has aged out of the window, and treat it as a suspected dropped CI event only if a retry still finds none. Neither is ever treated as green
exit status: 1
--- VERDICT: REFUSED - --allow-red cannot waive an absent check
Evidence: Stricter-only scenarios, each run against both the base and the branch script

Source: Stricter-only scenarios, each run against both the base and the branch script

A repository with NO pull_request CI keeps merging (empty rollup, hour-old head) [BASE] exit status: 0 | gh pr merge issued: YES [AFTER] exit status: 0 | gh pr merge issued: YES A green pull request whose pull_request-event run exists merges normally [BASE] exit status: 0 | gh pr merge issued: YES [AFTER] exit status: 0 | gh pr merge issued: YES A head committed 3 minutes ago: not arrived yet, re-check - never called a dropped event [BASE] exit status: 0 | gh pr merge issued: YES [AFTER] error: refusing to merge .../pull/203 - no pull_request-triggered check has reported for head 4a4a...4a4a yet, and its commit is younger than the delivery grace window; re-check shortly exit status: 1 | gh pr merge issued: no A pull_request_target-only repository keeps merging [BASE] exit status: 0 | gh pr merge issued: YES [AFTER] exit status: 0 | gh pr merge issued: YES An unreadable workflow listing disarms the gate loudly instead of refusing [AFTER] note: could not read this repository's workflow triggers, so the dropped-CI-event check is disarmed for this merge attempt exit status: 0 | gh pr merge issued: YES

Every scenario below is run twice against the same mocked GitHub state: once
with the base-commit (126eaec) merge script and once with this branch's, to
show the new rule only ever refuses MORE, never less, and never turns a
healthy pull request into an unmergeable one.

========================================================================
A repository with NO pull_request CI keeps merging (empty rollup, hour-old head)
========================================================================

[BASE] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/201
verified: https://github.com/example/repo/pull/201 is open and mergeable, with every required check green at head 1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/201 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

[AFTER] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/201
verified: https://github.com/example/repo/pull/201 is open and mergeable, with every required check green at head 1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/201 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

========================================================================
A green pull request whose pull_request-event run exists merges normally
========================================================================

[BASE] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/202
verified: https://github.com/example/repo/pull/202 is open and mergeable, with every required check green at head 3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/202 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

[AFTER] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/202
verified: https://github.com/example/repo/pull/202 is open and mergeable, with every required check green at head 3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/202 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

========================================================================
A head committed 3 minutes ago: not arrived yet, re-check - never called a dropped event
========================================================================

[BASE] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/203
verified: https://github.com/example/repo/pull/203 is open and mergeable, with every required check green at head 4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/203 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

[AFTER] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/203
error: refusing to merge https://github.com/example/repo/pull/203
  - no pull_request-triggered check has reported for head 4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a yet, and its commit is younger than the delivery grace window; re-check shortly
armed: state/task-x1.check.sh
exit status: 1   |   gh pr merge issued: no

========================================================================
A pull_request_target-only repository keeps merging
========================================================================

[BASE] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/204
verified: https://github.com/example/repo/pull/204 is open and mergeable, with every required check green at head 2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/204 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

[AFTER] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/204
verified: https://github.com/example/repo/pull/204 is open and mergeable, with every required check green at head 2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/204 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

========================================================================
An unreadable workflow listing disarms the gate loudly instead of refusing
========================================================================

[BASE] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/205
verified: https://github.com/example/repo/pull/205 is open and mergeable, with every required check green at head 6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/205 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES

[AFTER] $ bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/205
note: could not read this repository's workflow triggers, so the dropped-CI-event check is disarmed for this merge attempt
verified: https://github.com/example/repo/pull/205 is open and mergeable, with every required check green at head 6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a
armed: state/task-x1.check.sh
verified: https://github.com/example/repo/pull/205 is merged (state=MERGED, merged=true, isInMergeQueue=false)
exit status: 0   |   gh pr merge issued: YES
Evidence: The 12 new dropped-CI test cases, run case by case

Source: The 12 new dropped-CI test cases, run case by case

ok - fm-pr-merge merges normally when the repository has no .github/workflows directory ok - fm-pr-merge ignores a workflow with no pull_request trigger ok - fm-pr-merge never arms the dropped-event gate on a pull_request_target-only repository ok - fm-pr-merge never reads comment text as a pull_request trigger declaration ok - fm-pr-merge reads only on:'s own children as trigger declarations ok - fm-pr-merge recognises the 'on': trigger-key spelling ok - fm-pr-merge recognises a zero-indented sequence of triggers under on: ok - fm-pr-merge merges normally once a pull_request-event run exists at the head ok - fm-pr-merge treats a fresh zero-run head as not-yet-arrived, not a drop ok - fm-pr-merge never treats a suspected dropped CI event as green, even with --allow-red ok - fm-pr-merge refuses a zero-run head even when the head commit date cannot be read ok - fm-pr-merge does not refuse a merge merely because the workflow listing could not be read 12 cases run, 0 failed.

tests/fm-pr-merge.test.sh - the 12 cases this change adds, run case by case

ok - fm-pr-merge merges normally when the repository has no .github/workflows directory
ok - fm-pr-merge ignores a workflow with no pull_request trigger
ok - fm-pr-merge never arms the dropped-event gate on a pull_request_target-only repository
ok - fm-pr-merge never reads comment text as a pull_request trigger declaration
ok - fm-pr-merge reads only on:'s own children as trigger declarations
ok - fm-pr-merge recognises the 'on': trigger-key spelling
ok - fm-pr-merge recognises a zero-indented sequence of triggers under on:
ok - fm-pr-merge merges normally once a pull_request-event run exists at the head
ok - fm-pr-merge treats a fresh zero-run head as not-yet-arrived, not a drop
ok - fm-pr-merge never treats a suspected dropped CI event as green, even with --allow-red
ok - fm-pr-merge refuses a zero-run head even when the head commit date cannot be read
ok - fm-pr-merge does not refuse a merge merely because the workflow listing could not be read

12 cases run, 0 failed.
- Outcome: ⚠️ 1 warning across 1 run (21m16s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-pr-merge.sh:648 - A zero-indented YAML block sequence under on: silently fails to arm the gate, so the exact PR-34 failure this change exists to close stays reachable for those repositories. Concrete input, verified by running the awk from bin/fm-pr-merge.sh:635-655 directly: a repo whose only workflow is name: CI / on: / - push / - pull_request / jobs: {}. GitHub Actions parses this with a normal YAML parser and runs the workflow on every pull request, but github_workflow_declares_pull_request matches - push against the block terminator at line 648 (line ~ /^[^[:space:]]/ { in_on = 0; next }), because a block sequence is legally allowed at its parent key's own indentation. in_on is cleared before - pull_request is ever examined, the function exits 1, github_repo_has_pr_ci_workflow falls through to FM_PR_GITHUB_PR_CI=no at line 715, and the case at line 843 never reaches the yes arm. That repository's pull request whose pull_request delivery was dropped then merges on its empty-but-green-looking rollup, with no stderr note, since "no" is the proven-absence verdict and prints nothing. This contradicts the function's own header at line 627, which states that a - pull_request sequence entry counts as a trigger declaration - only the indented sequence form actually does (I confirmed on: / - push / - pull_request arms correctly). Remedy is a mechanical repair of the heuristic the change already implements: stop treating a line that begins a sequence entry as the block terminator (e.g. line ~ /^[^[:space:]-]/ { in_on = 0; next }), which leaves child at indent 0 and lets the existing ^[[:space:]]*(-[[:space:]]*)?pull_request[[:space:]]*(:|$) match fire. The same class covers a quoted &#34;pull_request&#34;: key, which also never matches; widening the key match is optional, but the sequence form is the common real-world spelling. auto-fix because this corrects the documented behavior of a component the intent requires, not the author's choice of rule.
  • ℹ️ bin/fm-pr-merge.sh:844 - Noting an accepted tradeoff, no action needed. Both pre-absence reads fail open: an unreadable workflow listing (a 403 secondary rate limit, a transient 5xx) at line 844 and an unreadable run count at line 850 each print a stderr note and let the merge proceed. So during a GitHub API disruption firstmate can still merge a pull request whose pull_request event was dropped - the intent's goal ("unable to treat an absent check as a passing one") holds only when the two reads succeed. The change makes this choice deliberately and documents it at lines 668-675 and 738-744, tests it (test_unreadable_workflow_listing_does_not_block_a_green_merge), and the alternative - refusing on an inconclusive read - would be a new refusal healthy pull requests cannot clear, which the intent's "a green PR must not become unmergeable" constraint forbids. Recording it so the residual window is visible, not asking for a change.

🔧 Fix: arm dropped-CI gate on zero-indented on: sequence triggers
1 info still open:

  • ℹ️ bin/fm-pr-merge.sh:653 - Residual coverage gap, noted as a tradeoff rather than a request to change. The child-key match ^[[:space:]]*(-[[:space:]]*)?pull_request[[:space:]]*(:|$) accepts only the bare event-name spelling, so a workflow written on: / &#34;pull_request&#34;: (or &#39;pull_request&#39;:) reads as no-PR-CI, FM_PR_GITHUB_PR_CI=no, and the gate never arms for that repository — silently, since "no" is the proven-absence verdict and prints no stderr note. I confirmed this by running the extracted awk: the quoted-key input returns 1 while every unquoted spelling returns 0. This is an asymmetry with the on: key itself, where all three of on, &#34;on&#34; and &#39;on&#39; are accepted at line 640. Two reasons this does not need action here: the failure is in the fail-open direction (it can never turn a healthy PR unmergeable, only leave the PR-34 class reachable for that repo), and quoting an event name has no YAML 1.1 motivation the way quoting on does, so it is a spelling essentially only tool-generated workflows produce. Round 1 raised this same gap and explicitly called widening it optional; the fix round implemented the mandatory sequence-entry half, which I verified works. Recording it so the boundary of the heuristic stays visible.
⚠️ **Test** - 1 warning
  • ⚠️ tests/fm-pr-merge.test.sh - tests/fm-pr-merge.test.sh could not be run to completion on this host: six consecutive attempts each aborted at a different pre-existing case (github-open-unqueued, github-outcome-read-fails, github-refusal-quotes-forge, github-unrecognised-queue-method, and once a half-sourced harness at tests/lib.sh:109). The failures are host-induced, not from this change: under the current load (load average 17-25, ~1250 processes from concurrent agents) subprocesses intermittently fail, which silently breaks the perl Cwd::realpath helper fm_backlog_canonical_existing in bin/fm-backlog-transition-lib.sh:664 and makes the shared pre-merge captain-hold / task-record directory resolution refuse. The same failures reproduce against the base-commit (126eaec) script, and a pre-existing untouched case failed 0/10 while the same runs flaked whenever load spiked. Every case passes in isolation. Remote CI on an unloaded runner still owns the full-file regression.
  • bash tests/fm-pr-merge.test.sh (full file; aborted on host-load flakes at pre-existing cases, see finding)
  • The 12 new cases run individually against the real script: test_no_workflows_directory_merges_unaffected, test_push_only_workflow_does_not_arm_the_dropped_event_gate, test_pull_request_target_only_workflow_does_not_arm_the_dropped_event_gate, test_commented_out_trigger_does_not_arm_the_dropped_event_gate, test_nested_input_named_pull_request_does_not_arm_the_dropped_event_gate, test_single_quoted_on_key_arms_the_dropped_event_gate, test_zero_indented_sequence_trigger_arms_the_dropped_event_gate, test_pr_ci_configured_with_a_run_present_merges_normally, test_dropped_ci_event_within_grace_window_is_not_actionable, test_dropped_ci_event_past_grace_window_refuses_as_suspected_drop, test_unreadable_head_commit_date_still_refuses_a_zero_run_head, test_unreadable_workflow_listing_does_not_block_a_green_merge - 12/12 pass
  • Manual before/after regression: bin/fm-pr-merge.sh task-x1 https://github.com/example/repo/pull/34 run against both git show 126eaec:bin/fm-pr-merge.sh and the branch script on identical mocked GitHub state (empty statusCheckRollup, on: pull_request: workflow, unfiltered run count 1 from a workflow_dispatch diagnostic, ?event=pull_request count 0, head commit 3600s old)
  • Manual retry of the same refused head with --allow-red ci to confirm the refusal is unwaivable
  • Manual stricter-only scenarios run against both script versions: no-PR-CI repo (workflows 404), green PR with a pull_request-event run present, head committed 180s ago, pull_request_target-only repo, and an unreadable workflow listing
  • Flake triage: test_verified_merge_records_pr_and_head and test_github_open_unqueued_outcome_refuses (untouched pre-existing cases) run 10-12x interleaved with the new cases to compare failure rates under host load
  • Setup fix: installed tasks-axi 0.2.5 into a temp npm prefix and prepended it to PATH, since bin/fm-captain-hold.sh refuses without a compatible build; removed afterwards
⚠️ **Document** - 1 info
  • ℹ️ docs/architecture.md:318 - Out-of-scope consolidation worth a follow-up, not a defect in this change. The dropped-CI gate's rationale now exists as two near-parallel full copies: the ~40-line header block at bin/fm-pr-merge.sh:16-56 and the 13-line narrative at docs/architecture.md:318-330. Both are currently accurate and mutually consistent — I verified each claim against the final code — but they restate the same five facts point-for-point (rollup is deliberately not read, pull_request_target does not arm because its run carries the base SHA, the ?event= filter defeats the workflow_dispatch trap, the grace window is measured from the head commit's own date and only picks wording, and the branches/paths deadlock is an accepted cost), so any future change to the gate must be landed in both places or one silently drifts. This duplication follows the existing convention in this section — docs/architecture.md:317 similarly restates github_checks_not_green's rule alongside the script — and docs/scripts.md declares the script header authoritative for behavior and contracts, so a fix means deciding the boundary between the two surfaces for this whole section rather than editing one paragraph. Proposing a follow-up that reduces docs/architecture.md to the mechanism boundary plus a pointer to the header, applied consistently across the PR-merge section. Deliberately not done here: it would be a documentation-architecture migration well beyond what this change made stale.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

GitHub's PR Checks summary reads identically for "no CI configured" and
"the pull_request event never reached Actions for this push," so an
empty statusCheckRollup was trusted as green. Gate GitHub merges on a
positively confirmed pull_request-triggered run at the current head
once the repo is known to have PR CI, filtering strictly on the
pull_request event so a manual workflow_dispatch diagnostic run can
never masquerade as arrived checks.
@rub-a-dub-dub
rub-a-dub-dub merged commit fc20562 into main Sep 20, 2026
14 checks passed
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.

1 participant