hooks/tests: a crashed hook is scored as "allow", so a dead safety guard reads as a passing test #366
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#366
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found this while diagnosing a red main. The CI
gatejob failed onhook-testswith one case out of 72:The same commit passed the same step in the pull-request run, and the suite passes locally on repeat runs, so the decision logic is not wrong. What the failure exposed is a harness problem that matters more than the flake.
The harness cannot distinguish "allowed" from "crashed"
hooks/tests/check-git-worktree.test.shscores every case like this:Three things go wrong together:
2>/dev/null), so a crash leaves no trace.pipefail, so a failure in eitherjqor the hook is invisible.allow, becausegot_blockis initialised toallowand only ever upgraded toblock.A hook that dies before printing anything therefore scores exactly the same as a hook that deliberately permitted the command. That is the wrong default for a suite whose entire job is guarding safety rails: the failure mode it is least able to detect is the guard being dead.
In this instance it produced a confusing red. The worse case is the silent one - a hook that crashes on some input would show up as
dec=allowon a test that expects allow, and pass.What I want
set -o pipefailaround the invocation, or run the hook separately from the input-buildingjq) and report a non-zero exit as its own distinct result, never as a decision.allow. An expect-allow case should assert the hook exited 0 AND produced no deny, not merely that no deny appeared.run,failopen,dir_remedy_check) and check the otherhooks/tests/*.test.shfiles for the same pattern.failopenis the one case that legitimately expects a clean exit with no deny, and it already checksrc -eq 0- that is the shape the others should follow.Acceptance criteria
jqfails, is reported as an error naming that, and never asalloworblock.Two behaviours in the merged harness were judgement calls rather than things the issue pinned. Recording them here so they are findable if either turns out wrong - both are reversible.
1. A suite whose guard never emits anything fails, by default.
Liveness is judged per suite, not per case, because within a single case an inert guard and a genuine allow are indistinguishable - both are "exit 0, no output". So the harness ledgers every invocation and, at finish, fails a suite where the guard stayed silent across all of them. All eight current suites have deny cases, so none is affected, verified over repeated runs.
HOOK_HARNESS_ALLOW_SILENT_SUITE=1opts out.The cost: a future suite that legitimately expects total silence from its guard goes red until someone sets that flag. The alternative shape is opt-in liveness (an explicit
hook_harness_expect_outputcall per suite), which can never false-red but lets a new suite default to having no liveness evidence at all - which is the gap this issue was about. Fail-loud-by-default seemed the right way round given that, but it is a preference, not a fact.2. A payload builder that writes to stderr while still producing valid JSON is a note, not a failure.
The structural check means a builder that actually corrupts the payload is a hard failure - the guard is not consulted and the suite reddens naming the cause. This softer path only covers the case where the built JSON is provably well-formed and the builder merely warned. Reddening on environment noise would trade this issue's flake for a new one, and it matches how the library already treats stderr from the guard itself.
A stricter reading of the defect would hard-fail on any builder stderr at all. Worth revisiting if a real corruption ever slips through as a note.
Neither is load-bearing for the fix itself; both are about where the harness sits on the noisy-vs-silent axis.