The auto-merge that dropped a security gate

Auto-merge does not automate the judgement “this change is correct”. It automates a different sentence: “this change was correct at some earlier moment”. Those are the same statement only while the base branch holds still — and on a repo taking several pull requests an hour, it does not.

What actually landed

The failure on this fleet was not a bad merge in any dramatic sense. It was textual. Git can auto-merge a workflow file “successfully” and still drop a job’s steps, because a merge is textual and the two edits were structurally different. Measured here: a rebase over a concurrent pull request silently removed a signature-verification gate from the one job that actually publishes — a green pipeline with a missing security gate.

Nothing about that outcome is visible in the result. The file has no conflict markers, the pull request is green, and the job that lost its gate still runs — without the thing that was protecting it.

A conflict resolution is worse, not better: it is new code that no test has seen. The merge commit is not the commit that passed CI, and most CI systems do not build the merge result unless you explicitly ask them to. So the artifact that reaches main is frequently a revision nobody evaluated.

Required checks are evaluated against a base that can move

The second half of the problem is in the forge. Required status checks are evaluated against the base branch as it was when they ran — and Forgejo does not re-run pull-request workflows when the base branch moves: it runs them on pull-request events (opened, synchronized, reopened). Nothing else.

A pull request that was green against main an hour ago therefore still shows green after several other pull requests landed underneath it. The check is a true statement about an earlier main. The workflow that exists because of this is explicit about it:

Forgejo does NOT re-run PR workflows when the BASE branch moves: it only runs them on PR events (opened / synchronize / reopened).

That comment sits at the top of .forgejo/workflows/pr-base-guard.yml in the public ymrtech/nix-config repository. The job inside re-checks the pull request head against the current origin/main using the same three-way merge the merge itself would perform — git merge-tree --write-tree — so a real conflict fails in seconds instead of surfacing at merge time as a 405, or after the merge as a post-merge deploy rollback.

Three details are worth stealing:

Make the safe path the cheap one

The counter-argument is that auto-merge exists because the manual step is boring, and a boring step that is merely required gets skipped. Discipline is not a control. So the fix is not “be more careful” — it is to leave auto-merge in place and put the verification where the work already happens.

The invariant is short: the gate must be re-evaluated against the merge RESULT, and a human reads every conflict resolution. The base guard is that idea made cheap — one [gate]-labelled job, no nix, seconds of runtime, unfiltered, so it runs on every pull request.

The other half is refusing to trust the merge call — this chain learned it the same way:

1
FATAL: PR #$LOCK_PR reported merged but main's flake.lock does not pin clientsv@$MYSHA

The comment above it records why: a 200 from the merge endpoint without the merge applying was reported as success, because the wait that followed matched a pre-existing deploy of the old main head. The lock never reached main and nothing noticed. The remedy has the same shape as everywhere else on this site — assert the effect, not the status code.

What you can check

This is the same argument as a green deploy that deployed nothing: the check is about a particular revision of a particular tree, and that deploy had computed its scope from a single commit’s diff. For the gate that verifies a change after it lands — the health check and the automatic rollback on the host itself — see how the stack is built.

󰔟 Running the same kind of stack?
The tunnel this site is written from is the one I sell: WireGuard to a gateway in Montréal, three DNS postures, one price — and the same "check it yourself" habit as everything above.
[ CREATE ACCOUNT ] [ WHAT IS THE VPN ]
󰣨 ymrtech@ymrtech | 󰌠 NixOS | 󰍢 UTF-8