tri: an absent compiler is exit 2 "could not run", not exit 1 "the check failed" - #3054
Merged
Conversation
…eck failed" Closes #3045 `.githooks/pre-commit` runs `scripts/tri check-now` under `set -e`. With t27c unbuilt the script exited 1 with "t27c not found" and "local commands still work", and the commit was refused. The NOW gate had not run. It found nothing wrong. An absent build artefact was reported as a failing pre-commit gate, in a message naming a build step rather than a blocked commit, with a second line that reads as reassurance. It fires on every fresh worktree, which is the normal state at the start of a unit of work. scripts/tri now exits 2 on that path. POSIX 1003.3 calls it UNSUPPORTED -- the environment lacks basic support for running the check -- and tools/check_now_entry_shape.py already uses 2 for the same thing one layer up, so the convention is this repository's rather than a new one. The hook catches 2 and says the gate did not examine the commit. The commit is still refused: a gate that could not run must not pass. `if ! cmd; then rc=$?` captures the NEGATION's code, which is 0. The first draft of the hook had exactly that, and it would have let the commit through with the gate unrun -- the defect being fixed, reintroduced by the fix. Probed with a two-line shell experiment rather than reasoned about; the probe is in the comment. The regression test builds its own compiler-less tree (scripts/tri derives REPO_ROOT from its own location, so a temp directory holding only scripts/ has none of the four binary paths) instead of asserting on whatever the ambient checkout contains. Three mutants, three kills, and the third is the point: making the branch unconditional satisfies every positive assertion and is caught only by the control, TRI_T27C pointing at a binary that exits 0. It also verifies the claim the deleted line made rather than repeating it: `tri help`, `tri loop-help` and `tri disk` all exit 0 with no compiler present. Runs in loop-tools-gate.yml, the one job with no compiler in it.
Contributor
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3045
What happened
No commit. Nothing else printed.
.githooks/pre-commitruns underset -euo pipefailand its first statement isbash scripts/tri check-now; that script exits 1 when it cannot resolvet27c, andset -eaborts the hook.The NOW gate did not run. It found nothing wrong. An absent build artefact is reported as a failing pre-commit gate, in a message whose first line names a build step rather than a blocked commit and whose second line reads as reassurance. It fires on every fresh worktree — the normal state at the start of a unit of work.
The change
scripts/triexits 2 on that path. POSIX 1003.3 calls it UNSUPPORTED — "the testing environment lacks basic support for compiling or running the test case" — and the convention is already this repository's, not a new one:tools/check_now_entry_shape.pyexits 2 with "Reporting nothing rather than a pass this run did not earn" for the same situation one layer up.The hook catches 2 and says what happened. The commit is still refused — a gate that could not run must not pass.
Verified, three arms
scripts/tri2, hook 2, prints "could NOT RUN … Nothing about your change was checked"tri help0,tri loop-help0,tri disk0scripts/tri0, hook 0,✅ NOW entry gate passedThe bug the fix nearly shipped
The first draft of the hook was
$?there is the negation's status, which is 0 — so the hook would have exited 0 and let the commit through with the gate unrun: the exact defect being fixed, reintroduced by the fix. Caught by probing, not by reasoning:The probe is in the comment above the line.
The test
It builds its own compiler-less tree rather than asserting on whatever the ambient checkout contains:
scripts/triderivesREPO_ROOTfrom its own location, so a temp directory holding onlyscripts/has none of the four binary paths. Deterministic on a developer machine with a build and in CI without one.an absent compiler exits 2, not 1FAILEDif true)control: a resolvable compiler is not reported as could-not-runFAILEDThat third mutant is why the control is there: it satisfies every positive assertion in the file.
It also verifies the claim the deleted line made instead of repeating it —
tri help,tri loop-helpandtri diskall exit 0 with no compiler present.Runs in
loop-tools-gate.yml, the one job that has no compiler in it, next to the step that already asserts the loop helpers dispatch without one.Noticed, not touched
scripts/tricarries the loop-helper dispatch block twice — lines 12–30 and lines 371–383. The first is live; the second is unreachable, because the firstexits orexecs for every input that reaches it. Its comment says the helpers are "dispatched before anything is forwarded to t27c", which is true of the copy above it and false of itself. Filed separately rather than deleted here.