Skip to content

R3 — refuse a merge onto a failing base, and one place to claim a repair - #89

Merged
matthewpmunger merged 3 commits into
mainfrom
chore/merge-order-guardrails
Aug 26, 2026
Merged

R3 — refuse a merge onto a failing base, and one place to claim a repair#89
matthewpmunger merged 3 commits into
mainfrom
chore/merge-order-guardrails

Conversation

@matthewpmunger

Copy link
Copy Markdown
Collaborator

Base commit

  • Base sha: 6f0076d36d3db5b27a8878a048e4751c0fc0046a
  • Checks at that sha: greennpm run lint, npm run typecheck,
    npm test -- --run (103 files / 1324 tests), npm run build all pass, after
    npx --yes npm@10.9.7 ci.

What & why

R3, merge order. main has broken four times in three days and never once
through a git conflict: two changes that did not overlap textually were merged,
and the breakage existed only in the combination neither branch had been tested
against. F4 renamed a field S7's fixtures still passed the old shape. F5 and R2
repaired the same three files independently and git took both copies, so #87
had to delete the duplicates.

CI already runs against the merge result — that is how the F4 case was caught.
Nothing here adds a check. No lint rule, no test, no product code. It
changes what an existing check result is allowed to mean, and where a repair is
claimed.

Changes

  • A merge onto a failing base is refused. .github/workflows/base-branch-gate.yml
    reads the CI result main already produced for its own tip and republishes it
    as the base-branch-green status on every open PR. Branch protection requires
    that context, so this is not a warning and not a reviewer's judgement.
    • It gates the merge, not the build. Chunks are not serialised: keep building,
      merge when the base is green.
    • It costs no round. When main's CI finishes, the gate re-runs and rewrites
      the status on every open PR, so a blocked PR clears itself without a push.
    • A PR labelled repair is exempt, because a repair is how a red branch
      becomes green. The label is a claim in REPAIRS.md, not a bypass.
  • A superseded run is no longer recorded as a failure. ci.yml's
    concurrency group was ci-CI-<ref>, so two merges a minute apart shared a
    group and the second cancelled the first. 1622ed7, the F5 merge, still
    carries a red X for a run cancelled 17 seconds in, mid-typecheck — a false
    record of a chunk shipping broken. Push runs now get a group per sha and are
    never cancelled; PR runs still supersede each other, where the abandoned sha
    is one nobody will merge. The gate also reads a cancelled base run as "not a
    failure" rather than as red.
  • REPAIRS.md is the one place a repair is claimed, before it is written.
    Named in AGENTS.md, read with two commands, seeded with the F4/F5/R2
    history that made it necessary — and with one open, unclaimed defect (below).
  • AGENTS.md now says, in the words a session will follow: run the checks
    at your base commit and report the sha before building; do not inherit an
    earlier session's failing-check list; if the base is red, say so and stop; do
    not repair a red base inside a feature chunk; claim a repair first and keep it
    in its own commit. The PR template asks for the base sha; CONTRIBUTING.md
    points at both.

Verification

  • npm run lint passes (at base 6f0076d)
  • npm run typecheck passes
  • npm test -- --run passes
  • npm run build passes
  • Gate verdict exercised against real history with
    DRY_RUN=1 .github/scripts/base-branch-gate.sh <branch>:
    green base → success, 728eb6c (failed run) → failure,
    1622ed7 (cancelled run) → success, "last run was cancelled, not a
    failure".
  • Refusal demonstrated once on a scratch branch: a PR whose base branch's
    pipeline is failing cannot be merged (see the comment below).
  • n/a No route change: no product code in this PR.

Notes

  • Found, not fixed: DECISIONS.md on main contains literal unresolved
    conflict markers (lines 3–13), committed in aeeca8b and shipped green
    because no check reads markdown. It is logged as the open row in REPAIRS.md
    rather than repaired here — repairing shared breakage inside another chunk's
    diff is the thing this PR exists to stop. It is one paragraph and the ledger
    row says which side is true.
  • Fork PRs get a read-only token and cannot post the status, so the required
    context stays absent and the merge is refused rather than waved through.
    Chunk sessions push branches to this repo.

Two merges landing a minute apart shared a ref, so cancel-in-progress had the
second cancel the first and GitHub recorded the cancelled run as a red check
against the commit it superseded. 1622ed7 (the F5 merge) still carries an X for
a run cancelled 17s in, mid-typecheck, which is a false record of a chunk
shipping broken.

PR runs still supersede each other -- the sha they abandon is one nobody will
merge. Push runs on main are the record of what a merge shipped, so they are
grouped per sha and never cancelled.
…m a repair

main has broken four times in three days, never through a git conflict: two
changes that did not overlap textually, merged, broken only in the combination
neither branch was tested against. CI already runs against the merge result --
that is how the F4 case was caught. Nothing here adds a check. It changes what
an existing check result is allowed to mean.

- base-branch-gate.yml republishes main's own last CI result as the
  base-branch-green status on every open PR. Branch protection requires it, so
  a merge onto a failing base is refused rather than argued about. It gates the
  merge, not the build: the check re-posts itself when main's CI finishes, so a
  blocked PR clears without pushing anything.
- A repair-labelled PR is exempt, because a repair is how a red branch becomes
  green. A cancelled base run is not read as a failure.
- REPAIRS.md is the one place a repair is claimed, before it is written, so the
  second session to find a defect rebases onto the first fix instead of writing
  its own. F5 and R2 both fixed F4's caller sweep, git found no conflict, took
  both copies, and #87 had to delete the duplicates.
- AGENTS.md now says to run the checks at your base commit and report the sha,
  not to inherit an earlier session's failing-check list, and to stop rather
  than repair a red base inside a feature chunk.
The job succeeds whenever it manages to publish -- including when what it
publishes is a failure. Sharing a name with the commit status meant branch
protection could require the job instead, which is green either way.
@matthewpmunger
matthewpmunger merged commit 70bfbb3 into main Aug 26, 2026
6 checks passed
@matthewpmunger
matthewpmunger deleted the chore/merge-order-guardrails branch August 26, 2026 18:21
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