Skip to content

fix(gates): the runner enabled errexit 27 times and called it a restore (#243) - #256

Merged
rubenvdlinde merged 1 commit into
mainfrom
fix/gates-empty-scope-never-pass
Aug 8, 2026
Merged

fix(gates): the runner enabled errexit 27 times and called it a restore (#243)#256
rubenvdlinde merged 1 commit into
mainfrom
fix/gates-empty-scope-never-pass

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Closes #243.

The defect

run-hydra-gates.sh runs under set -u only. errexit is deliberately OFF, because a gate returning non-zero is how a gate reports findings.

Twenty-seven blocks wrapped a helper call in set +e … set -e and read the trailing set -e as "restore". It is not a restore — it is an unconditional ENABLE, because errexit was never on in the first place. The first offender sits in gate-19, so gates 20–64 — forty-five gates — ran under an errexit the surrounding code does not expect.

The file already knew. Two comments work around the leak instead of fixing it:

  • gate-27: NOTE: gates 25/26 above leave 'set -e' ENABLED, so a 'grep -c .' on an empty log would kill the script here.
  • gate-53: NOTE: 'set -e' is still enabled at this point in the script (gates 25/26 leave it on) — every command below is guarded.

Reproduction

A fixture app plus a python3 shim that exits 127 — standing in for the checker being uninstalled, OOM-killed, argv-too-long, or syntactically broken. gate-39's unguarded python3 - "$vue" <<'PYBN' returns non-zero, errexit is live, and the run dies there:

before after
gates reporting a verdict 37 of 64 64 of 64
gates never executed 27 0
last verdict [gate-38] skip-link: PASS [gate-64] …
coverage summary reached no yes

The abort banner does fire, so the run is not silently green. It is a whole-suite outage triggered by one checker having a bad day.

The fix

  • every restore site now says set +e — the state the script actually runs in
  • _pass / _fail / _skip re-assert set +e as a backstop: every gate ends at one of these three, so a future gate that leaks cannot carry the leak past its own verdict line
  • scripts/lib/test_gate_errexit_discipline.sh asserts both a static arm (no bare set -e) and a behavioural arm (full run, crashing checker, all gates still report)

Mutation check

The test takes HYDRA_GATES_RUNNER_UNDER_TEST so the fix can be checked against a known-bad copy without editing the shipped file. Against pristine origin/main all seven assertions go red, with the exact measured numbers.

Worth recording: a single-site mutation is not enough to red the behavioural arm — the remaining set +e sites switch errexit back off a few gates later. That is precisely why this leak survived long enough to be documented twice and fixed neither time, and why the honest mutant is the whole pre-fix file.

Coordination

No overlap with #247 on any errexit line (verified: git diff origin/main...pr247 touches none of them).

🤖 Generated with Claude Code

…re (#243)

run-hydra-gates.sh runs under `set -u` only — errexit is deliberately OFF,
because a gate returning non-zero is how a gate reports findings.

Twenty-seven blocks wrapped a helper call in `set +e … set -e` and read the
trailing `set -e` as "restore". It is not a restore, it is an unconditional
ENABLE: errexit was never on. The first offender is in gate-19, so gates 20-64
— forty-five gates — ran under an errexit the surrounding code does not expect.
Two comments in the file (gate-27, gate-53) already documented the leak and
worked around it locally instead of fixing it.

Measured against a fixture with a `python3` that exits 127: gate-39's unguarded
`python3 - "$vue" <<'PYBN'` returned non-zero, errexit was live, and the run
DIED there — 37 of 64 gates emitted a verdict and 27 never executed. The abort
banner does fire, so the run is not silently green; it is a whole-suite outage
triggered by one checker having a bad day.

- every restore site now says `set +e`, the state the script actually runs in
- `_pass` / `_fail` / `_skip` re-assert `set +e` as a backstop, so a future gate
  that leaks cannot carry the leak past its own verdict line
- scripts/lib/test_gate_errexit_discipline.sh asserts both arms, and takes
  HYDRA_GATES_RUNNER_UNDER_TEST so the fix can be mutation-checked against a
  known-bad copy without editing the shipped file

Mutation-checked against the pre-fix runner at origin/main: aborts at gate-38,
37 of 64 gates report, no coverage summary. After: 64 of 64 report, no abort.
A single-site mutation is NOT enough to red the behavioural arm — the remaining
`set +e` sites switch errexit back off a few gates later, which is exactly why
this leak survived long enough to be documented twice and fixed neither time.
@rubenvdlinde
rubenvdlinde merged commit ee1fb16 into main Aug 8, 2026
30 checks passed
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.

gate runner can abort mid-run: gate-25 leaks 'set -e' into every later gate (38 gates never executed)

1 participant