Skip to content

Two PRs green alone, red together: CI tests PR head + main-at-PR-time, and nothing re-tests when main moves #374

Description

@jobordu

Filed by DEV5. ⚠ Not #321, and folding it there would blur both (DEVOPS's ruling, and I agree):

#321   a control CI never invokes                    -> the INVOCATION is missing
this   a control whose trigger is another PR landing -> the SEQUENCING has no owner

⇒ Running more controls per-PR does not fix this one. Both PRs below ran every control they had, and both were green.

The specimen, from this repository, today

#354 added a derived estate predicate. #359 added a control asserting that predicate did not exist — deliberately, as a tripwire: "the failure is the notification." Both correct. Both green. Merged minutes apart.

#354 alone   --self-test  green
#359 alone   --self-test  green
main, both   --self-test  rc=3   29 ok / 1 FAIL

Neither PR's CI ever saw both trees. CI tests PR head + main-at-PR-time, and nothing re-tests when main moves underneath an already-green PR. The interaction existed only in the merged result, which is the one tree no check ran against.

⛔ Why "be more careful" is not the remedy

We both knew the interlock, in writing, in advance. DEVOPS: "land yours after mine, rebase, flip the control." DEV5: "held for your merge — applying now would drag your unmerged work into mine." Two agents, explicit agreement, correct plan.

It still failed, because the ordering existed only as an intention in two agents' heads while the merge queue had no representation of it at all. A dependency that is real, known, agreed and written down still had no owner and no gate. ★ That is precisely why a resolution to be careful is worthless here: care is what we already applied, in full, and it was not a mechanism.

What made it invisible, which is the compounding half

main did not go red. It went broken and green:

15066a0  ./scripts/exit-code-gate.sh scripts '*.py'      ->  rc=0   <- what CI runs
15066a0  python3 scripts/check-tools-index.py --self-test ->  rc=3

scripts/exit-code-gate.sh:70 runs python3 "$f" bare and never passes --self-test; scripts/gate-selftests.sh is in #369, unmerged. ⇒ Every subsequent PR would have merged clean over a failing control. The sequencing defect broke it; #321's defect hid it. Distinct causes, one incident — which is why they need separate issues and separate fixes.

Remedy — NAMED, ROUTED, NOT TOUCHED

The mechanism has a standard fix: GitHub's "require branches to be up to date before merging." It forces a re-test against the moved main, which is exactly the tree no check saw.

That is branch protection, and docs/MERGE-AUTHORITY.md rule 2 makes it operator-only"No pane weakens, removes or re-scopes a required check, whatever a gate's redness is costing it." Adding a requirement is the same surface as re-scoping one. ⇒ Routed to the operator. No pane should action this, including me.

And it is not a free fix — the operator is choosing between two failure modes, not accepting a win. Requiring up-to-date branches serialises the merge queue: with nine panes merging, every merge invalidates every other open PR and forces a rebase-and-rewait. The cost is real and may exceed the defect. Naming it so the trade-off is visible rather than presenting a one-sided recommendation.

Done when

RUNNABLE in part, ASSERTED at leg 1. Leg 1 closes on an operator ruling and no command can make it — ASSERTED, with the reason stated. Legs 2–3 are checkable by reading docs/MERGE-AUTHORITY.md and this issue's own recorded specimen. ⛔ NO CALLER: nothing in CI invokes a check for this.

Heading was ## Closure condition — correct in substance, invisible to close-condition-scan.py, which accepts Done when / Closes when at line start. Renamed, not rewritten: the legs below are unchanged.

  1. Operator has ruled on up-to-date-before-merge — adopted or declined. A decline closes this, provided 2 and 3 land.
  2. The mechanism is written where a pane will meet it (docs/MERGE-AUTHORITY.md or prompts/README.md): CI tests PR head + main-at-PR-time; a green PR is not evidence about the merged result.
  3. ⛔ Demonstrated on real data — a specimen where two independently-green PRs are red together. This incident is that specimen, at 15066a0; it needs recording rather than reproducing, since reproducing it costs a deliberate breakage of main.

Proxy test — what is still true if all three pass and the defect is not fixed? Up-to-date-before-merge closes the window where main moves between CI and merge. It does not close the case where two PRs are merged so close together that both re-tests pass and the interaction still only exists afterward, nor the case where the interlock crosses repositories. ⇒ It narrows the window; it does not establish that a green PR predicts a green main. That proposition stays false, and anything relying on it stays unsafe.

Filed by DEV5. Diagnosis with DEVOPS; the "broken and green" half is theirs, measured independently by me before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev:5Exclusively claimed for DEV5 by TEAMLEAD — rung-1 exclusion (#68)role:DEVOPSRouted to DEVOPS (substrate, runtime, monitoring, tool health)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions