Two green PRs can merge into a red `main` — nothing requires a branch to be up to date, so a stale check decides the merge
Opened by Zhu Yue ·
The defect, with the instance that produced it
main was left un-typecheckable for ~40 minutes on 2026-08-31, red-lighting the TypeScript check on every open pull request. Neither pull request involved was broken. Their sum was:
11:40:02Z | #2480 (MOTIR-1789) TypeScript goes green. ProjectRoadmapCanvas does not require searchLabel yet. |
12:09:16Z | #2486 (MOTIR-4016) merges — "the search box's own words" — making searchLabel required whenever searchable is true. |
12:34:47Z | #2480 merges, on a 55-minute-old green check taken against a main that no longer exists. |
One branch tightened a prop contract; the other added a consumer of it. They touch different files, so git merges them cleanly and reports no conflict; the type error exists only in the composed tree. Every signal a reviewer looks at was honestly green.
Repaired by MOTIR-4047 / PR #2490 — that is the patch. This card is the control.
Why a patch is not the fix
The class recurs whenever two pull requests touch opposite ends of one contract, which is routine on a repository with a shared component library and several concurrent agents. Nothing in the current setup notices: a PR's checks are computed against the main of the moment they ran, and nothing re-computes them at merge time.
The global lesson "Green on two branches is not green on their sum" (cp1p6s78ya8pll8x1k9zgwy7q) reaches the same conclusion in its own words — "the finding is a merge-order class and the remedy is a repository control, not a patch". This is its second recorded occurrence.
The control
GitHub branch protection on main: "Require branches to be up to date before merging", alongside the existing required checks. It forces a branch whose base has moved to re-run against the current tip before the merge button works.
⚠️ It is not free, and the cost should be decided rather than discovered. On a busy day every merge invalidates every other open PR's checks, so each one re-runs — and this repository's CI is long (E2E legs at 5–10 minutes each). Options worth weighing before flipping it:
- Require up-to-date, full stop. Strongest, most expensive.
- Require it only for the cheap, decisive checks —
TypeScript,Lint + Prettier,Structural guards— if branch protection can be scoped that way, which is worth checking rather than assuming. - A merge queue, which batches and tests the composed tree once instead of re-running every branch.
Acceptance criteria
- The setting on
mainis DECIDED and the reasoning recorded — including, if the answer is "not worth the CI cost", that answer with its numbers. - If enabled: the measured effect on merge throughput is quoted after a day, against the cost this card names.
docs/decisions/carries the decision, so the next person to meet a two-green-PRs-one-red-main incident finds the reasoning rather than re-deriving it.executor: human, and this is not incidental: it is a repository settings change no agent can make. Do not close it with code.
Context refs
motir-corePRs #2480, #2486, #2490 — the instance, end to end.github/workflows/ci.yml— the required checks and their cost- MOTIR-4047 — the patch, and the full timeline in its PR body
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.