Skip to content

fix(standard.mk): gates accumulate instead of aborting at the first red one - #19

Merged
hunterdsp merged 1 commit into
mainfrom
fix/gates-accumulate
Aug 11, 2026
Merged

fix(standard.mk): gates accumulate instead of aborting at the first red one#19
hunterdsp merged 1 commit into
mainfrom
fix/gates-accumulate

Conversation

@hunterdsp

Copy link
Copy Markdown
Contributor

Closes #18.

Reproduced the report exactly, in a fixture with a red gate mid-list: first and red run, behind1 and behind2 never do.

The proposed patch works on three of its four claims

claim verdict
every gate runs
a failing gate still fails the run (exit 2)
ALL PASS cannot print with one red
gates-check is unaffected it breaks

gates-check builds its closure by walking prerequisites out of make's database:

for p in $(sed -n "s/^$$t:[ ]*//p" "$$db" | sed 's/|.*//'); do

Move $(GATES_DEPS) into the recipe and that edge no longer exists, so the closure comes back empty and every CI target reports as uncovered. gates-check hangs off lint, so that is every adopter's make lint going red on the same fetch — the exact cost this repo's CI comment says only this job can catch.

Fixed by seeding the closure from $(GATES_DEPS) itself, which makes the check independent of how gates invokes its list — what it was always trying to assert.

Also addressed from the issue's open questions

  • -j: verified. make -j4 gates runs all four and still exits 2. The -k re-invocation keeps them schedulable in parallel; a shell loop over the list (the other obvious shape, and the one that would give a nicer failure summary) would have serialised them, which matters for doppler where gates include coverage and docker.
  • Noise: added --no-print-directory, or every run gains Entering directory/Leaving directory around the gate output.

Gate

New CI step asserting all four properties, sabotage-checked in both directions:

  • revert to prerequisites → fails with behind1 never ran
  • apply -k without the closure seeding → fails with gates-check cannot see GATES_DEPS any more

Existing standard.mk CI steps re-run locally against the edited file and all still pass — including the #13 recursion guard, since -rpn still targets .std-db-goal and never runs the gates recipe.

Before merging — this publishes

Pages serves this file, and adopters compare against it on every make lint with no cache. The moment this lands, standard-check fails in every subscriber until each re-vendors. Known adopters: just-makeit (re-vendor PR ready to follow immediately) and doppler (a different working lane — needs its own re-vendor, and make lint there is red in between).

Happy to hold this until that ordering suits.

Not addressed, per the issue

glibc-check failing by design on every dev box is left alone — separable, as the issue says, and with -k it can stay honestly red without taking six gates down with it.

`gates` declared GATES_DEPS as prerequisites, so make stopped at the first
failure and every gate ordered behind it never ran. That reads as an ordinary
failure while a third of the set was silently skipped. Measured in doppler:
`glibc-check` cannot pass on a modern dev box BY DESIGN, sits mid-list, and
made the six targets behind it structurally unreachable -- a broken `coverage`
hid there for weeks.

`gates` now re-invokes its list with `-k`. Not a weakening: the sub-make still
exits non-zero if any gate failed, so the run fails and `ALL PASS` cannot
print, and under `-j` the gates still schedule in parallel -- which a shell
loop over the list would have serialised. `--no-print-directory` keeps the
recursion from adding Entering/Leaving noise.

AND the part the issue expected to be free: `gates-check` breaks.

It builds its closure by walking PREREQUISITES out of make's database
(`sed -n "s/^$$t:[ ]*//p"`), so moving the list into the recipe leaves it
walking an empty edge -- every CI target then reports as uncovered, and since
`gates-check` hangs off `lint`, that is every adopter's `make lint` going red
on the same fetch. Measured, not reasoned: the patch as proposed fails
gates-check in a fixture.

So the closure is seeded from `$(GATES_DEPS)` itself. That makes the check
independent of HOW gates invokes its list, which is what it was always
asserting.

CI proves four properties, because three of them are what make the current
form safe and a fix that drops one is not a fix: every gate runs, a red gate
still fails the run, ALL PASS cannot print while one is red, and a clean run
still passes. Sabotage-checked in both directions -- reverting to
prerequisites fails on `behind1 never ran`, and applying `-k` WITHOUT the
closure seeding fails on `gates-check cannot see GATES_DEPS any more`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hunterdsp
hunterdsp merged commit 69003d1 into main Aug 11, 2026
2 checks passed
@hunterdsp
hunterdsp deleted the fix/gates-accumulate branch August 11, 2026 14:10
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.

standard.mk: gates should accumulate, not abort — one red gate hides every gate behind it

1 participant