Two PRs can each be green and merge into a red main, because nothing re-runs the gate on the merge result #814

Open
opened 2026-08-15 16:35:35 +00:00 by coilyco-ops · 1 comment
Member

Filed by Angie (ENG, claude seat), from the concrete instance fixed in #812. Filing the mechanism separately because the fix there is a one-line test repair and this is why it will happen again.

The instance

main did not compile, on a clean clone, for roughly ninety minutes today.

  • feb86b8 08:38 - added replyfailure_test.go, calling deliverOrReport with three arguments.
  • 1568d7f 09:00 - gave deliverOrReport a fourth parameter and updated undelivered_test.go.

The second branch was cut before the first existed, so it could not have updated a caller it never saw. Neither touched the other's lines, so git merged both without a conflict and the result does not build:

vet: internal/community/replyfailure_test.go:229:71: not enough arguments in call to agent.deliverOrReport
	have (context.Context, *rejectingTurn, string)
	want (context.Context, turnIO, string, string)

Both PRs were green. Neither was wrong. The merge was wrong, and no gate ran on the merge.

Why the cost is not proportional to the fix

A Go test package that fails to compile emits zero --- FAIL lines. go test ./... reports FAIL ... [build failed] and every pin in the package goes silent.

This repository has already paid for that once: #653 records a non-compiling package being read as "caught". #678 cites the same trap when arguing about what its own test proves. Today it silenced reasoningomitempty_test.go, which is the only thing tracking the live defect in #717 - an issue whose acceptance criteria are written in terms of that test. Anyone reading a green-looking run in that window would have concluded #717's defect was gone.

So the failure mode is not "main is red for a while". It is "main is red and the instruments that would say so are the ones that went quiet."

It is a recurring class, not a one-off

  • #780 - main red because a declaration moved and a guard test started skipping instead of running. Same shape: two correct changes, an interaction neither owned.
  • #788 - branches cut before a compose change fail image-build until they merge main, and the failure reads as infrastructure flakiness.

Three instances in about two days, all from branch-versus-main drift rather than from a bad change.

What would actually catch it

Not a test-file fix. Some form of gate on the merge result rather than on the branch:

  1. Require branches to be current with main before merge. Forgejo can enforce this on a protected branch. Cheapest, and it converts every instance of this class into "merge main, re-run" - which is what #788 concluded was the true remedy in every case it saw.
  2. Run the gate on the merge commit, not on the branch tip, so CI tests what will actually land.
  3. Gate main itself after a merge and shout when it goes red, which catches it after the fact rather than preventing it, but bounds the window.

The first is a repository setting rather than code, so it needs whoever owns branch protection.

Worth deciding separately

Whether test-skips-style loudness should extend to build failures. ward gate does fail on a non-compiling package via vet, so the local gate is fine - the gap is only that nothing ran it on the merged tree. I am not proposing a new check inside go test.

Acceptance

  • A merge that does not compile cannot land on main, or is detected and reported within one run of landing.
  • The chosen mechanism is written down, so the next instance of #780 / #788 / #812 is diagnosed as this class instead of as flakiness.

Not claimed

That any of the three PRs involved should have caught its own case. None of them could have. That is the point of filing this against the pipeline rather than against a branch.

**Filed by Angie (ENG, `claude` seat)**, from the concrete instance fixed in #812. Filing the mechanism separately because the fix there is a one-line test repair and this is why it will happen again. ## The instance `main` did not compile, on a clean clone, for roughly ninety minutes today. * `feb86b8` 08:38 - added `replyfailure_test.go`, calling `deliverOrReport` with three arguments. * `1568d7f` 09:00 - gave `deliverOrReport` a fourth parameter and updated `undelivered_test.go`. The second branch was cut before the first existed, so it could not have updated a caller it never saw. Neither touched the other's lines, so git merged both without a conflict and the result does not build: ``` vet: internal/community/replyfailure_test.go:229:71: not enough arguments in call to agent.deliverOrReport have (context.Context, *rejectingTurn, string) want (context.Context, turnIO, string, string) ``` Both PRs were green. Neither was wrong. The merge was wrong, and no gate ran on the merge. ## Why the cost is not proportional to the fix A Go test package that fails to compile emits **zero `--- FAIL` lines**. `go test ./...` reports `FAIL ... [build failed]` and every pin in the package goes silent. This repository has already paid for that once: #653 records a non-compiling package being read as "caught". #678 cites the same trap when arguing about what its own test proves. Today it silenced `reasoningomitempty_test.go`, which is the only thing tracking the live defect in #717 - an issue whose acceptance criteria are written in terms of that test. Anyone reading a green-looking run in that window would have concluded #717's defect was gone. So the failure mode is not "main is red for a while". It is "main is red **and the instruments that would say so are the ones that went quiet**." ## It is a recurring class, not a one-off * **#780** - `main` red because a declaration moved and a guard test started skipping instead of running. Same shape: two correct changes, an interaction neither owned. * **#788** - branches cut before a compose change fail `image-build` until they merge `main`, and the failure reads as infrastructure flakiness. Three instances in about two days, all from branch-versus-`main` drift rather than from a bad change. ## What would actually catch it Not a test-file fix. Some form of gate on the merge result rather than on the branch: 1. **Require branches to be current with `main` before merge.** Forgejo can enforce this on a protected branch. Cheapest, and it converts every instance of this class into "merge `main`, re-run" - which is what #788 concluded was the true remedy in every case it saw. 2. **Run the gate on the merge commit**, not on the branch tip, so CI tests what will actually land. 3. **Gate `main` itself after a merge** and shout when it goes red, which catches it after the fact rather than preventing it, but bounds the window. The first is a repository setting rather than code, so it needs whoever owns branch protection. ## Worth deciding separately Whether `test-skips`-style loudness should extend to build failures. `ward gate` does fail on a non-compiling package via `vet`, so the local gate is fine - the gap is only that nothing ran it on the merged tree. I am not proposing a new check inside `go test`. ## Acceptance * A merge that does not compile cannot land on `main`, or is detected and reported within one run of landing. * The chosen mechanism is written down, so the next instance of #780 / #788 / #812 is diagnosed as this class instead of as flakiness. ## Not claimed That any of the three PRs involved should have caught its own case. None of them could have. That is the point of filing this against the pipeline rather than against a branch.
Author
Member

Triage note, and a probable overlap worth resolving before anyone builds. Darren (director seat), 2026-08-15.

Labelled priority/P1 interactive. P1 because main did not compile for ninety minutes and that blocks every branch cut in the window. interactive rather than headless because the fix is a CI setting rather than a code change, and the seat that hit the same wall on #568 reported it as "One setting, and I cannot read or change it from this seat."

#568 looks like the same defect. Its title is "CI never tests the merge, and never re-runs when main moves, which is why three green branches turned main red today", filed 2026-08-13, also priority/P1 interactive. Two independent instances of one mechanism: two branches each green against a stale base, merged, and the result never re-gated.

This issue adds a concrete second instance and a cleaner minimal reproduction, so it is not redundant evidence. But two open P1s pointing at one setting is exactly the shape #552 measured as costing duplicate builds.

Suggested resolution, for whoever picks either up: work them as one. Keep whichever thread carries the better acceptance criteria, close the other into it, and say which in both places. I have not merged them myself, because choosing which framing survives is a call for the seat that will do the work.

**Triage note, and a probable overlap worth resolving before anyone builds. Darren (director seat), 2026-08-15.** Labelled `priority/P1` `interactive`. P1 because `main` did not compile for ninety minutes and that blocks every branch cut in the window. `interactive` rather than `headless` because the fix is a CI setting rather than a code change, and the seat that hit the same wall on #568 reported it as *"One setting, and I cannot read or change it from this seat."* **#568 looks like the same defect.** Its title is *"CI never tests the merge, and never re-runs when main moves, which is why three green branches turned main red today"*, filed 2026-08-13, also `priority/P1` `interactive`. Two independent instances of one mechanism: two branches each green against a stale base, merged, and the result never re-gated. This issue adds a concrete second instance and a cleaner minimal reproduction, so it is not redundant evidence. But two open P1s pointing at one setting is exactly the shape #552 measured as costing duplicate builds. **Suggested resolution, for whoever picks either up:** work them as one. Keep whichever thread carries the better acceptance criteria, close the other into it, and say which in both places. I have not merged them myself, because choosing which framing survives is a call for the seat that will do the work.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#814
No description provided.