Forgejo does not re-run your pull request when main moves

A green check next to a pull request is one of the most over-read marks on a forge. It does not mean “this change is good to merge”. It means something much narrower: the commit at the head of this PR, merged with the main that existed when the run started, passed the gates. The second half of that sentence is the part that expires.

Forgejo only runs PR workflows on PR events

The check that guards every PR in the nix-config repository states the reason in its own header. .forgejo/workflows/pr-base-guard.yml, first comment block:

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

synchronize is an event you cause by pushing to the branch. A merge landing on main is an event on a different branch, so it does not re-trigger your PR’s workflows. The badge does not go stale, because the forge has no concept of a check describing an older main — from its side, the check is simply green.

What the guard computes

The guard exists for exactly that gap, and it computes the merge instead of guessing at it:

1
2
3
4
git merge-tree --write-tree --no-messages --name-only origin/main <branch>
# exit 0 = merges clean
# exit 1 = real conflict
# exit >1 = git could not compute the merge at all

The third code is the one worth reading twice. >1 means git could not perform the merge — unrelated histories being the classic case — and the guard fails that as unmergeable, with its own message, rather than folding it into “conflicts”. The remedy it prints is not a rebase: an unmergeable branch has to be recreated from origin/main and the change re-applied.

Three details make the job trustworthy:

Conflict is the loud case; clobbering is the quiet one

A red Base drift / conflict check is the case people expect: rebase, resolve, push. The guard goes further. When the merge is clean it also compares the files your branch changed since the merge base against the files main changed in the same window, and emits a ::warning:: when they overlap. It stays green on purpose — with several PRs an hour, failing every behind-but-clean branch would force every open PR to rebase and re-run its per-host closure builds whenever anything else merges.

That warning carries the real risk. In a repository where more than one agent session edits at once, “textually clean” is not “semantically safe”. The guard’s own failure text says not to merge main into the branch as a shortcut, and to resolve on the file’s new structure rather than re-instating your copy, because in this repo a conflict usually means main rewrote that file underneath you. A merge that applies your hunk to a file someone restructured two merges ago can land green and quietly undo their change.

The merge endpoint agrees with the guard. A merge returning 405, or a PR reading mergeable:false, almost always means the branch is behind main — not that the forge is broken.

Why this is the default hazard, not an edge case

A shared, fast-moving repository turns base drift from an edge case into the normal state. When several sessions and several PRs land per hour, a branch cut twenty minutes ago is already behind, and every merge to main invalidates the evidence under every other open PR. The discipline this fleet runs on follows directly: fetch and rebase onto origin/main immediately before every push, and re-verify the base immediately before merging — the only pre-merge check that means anything is one computed against the current main.

So the pre-merge check is not the badge. It is, in the moment before you merge:

1
2
git fetch origin
git merge-tree --write-tree --no-messages --name-only origin/main <branch>

and when that returns non-zero: rebase onto the freshly fetched origin/main, re-apply the change onto the file’s new shape rather than restoring your copy, re-run the gates, then git push --force-with-lease.

What you can check

For the runner pools and the host graph these jobs run on — and why the guard floats on the light gate label instead of a host’s single slot — 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