Found by a measured mutation run while piloting the detector-findings producer contract (#2680). Filed separately because it is a real defect in this repo's own tooling, independent of that pilot.
The gap
scripts/check-skill-precompute-compose.sh:30:
while [[ $# -gt 0 ]]; do
case "$1" in
--strict) STRICT=1; shift ;;
-h | --help) usage ;;
-- | --all | --paths)
POSITIONAL+=("$1")
shift
break
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
Mutating -gt 0 to -ge 0 survives the full suite (scripts/check-skill-precompute-compose.test.sh, baseline green, 5 tests).
It survives because every existing test enters through the -- | --all | --paths) branch, which breaks out of the loop before $# reaches 0. Nothing drains arguments through the *) fall-through, so the loop's termination condition is unasserted.
Why it matters
The script runs set -uo pipefail (line 17). With the mutation, once $# reaches 0 the loop body still executes and case "$1" hits an unbound variable. Measured behavior of the mutant, run from a real checkout:
| Invocation |
Original |
Mutant |
| (no args) |
usage, exit 2 |
line 31: $1: unbound variable, exit 1 |
--strict |
usage, exit 2 |
line 31: $1: unbound variable, exit 1 |
<base-ref> |
scans, exit 0 |
line 31: $1: unbound variable, exit 1 |
--strict <base-ref> |
scans, exit 0 |
line 31: $1: unbound variable, exit 1 |
--all |
scans, exit 0 |
scans, exit 0 (identical — break fires first) |
So five of six invocation shapes break outright and the suite still reports green. The surviving mutant is not itself interesting; the oracle gap it reveals is — the argument parser has no coverage except through the one branch that short-circuits it.
Suggested fix
Add a case to scripts/check-skill-precompute-compose.test.sh that invokes the script with a bare base-ref argument (no --all, no --paths) and asserts it reaches the scan rather than erroring. A case passing --strict ahead of a base-ref would cover the *)-then-drain path as well.
Reproduction
Copy the script and its test into a scratch directory (the test resolves its subject as $(dirname "$BASH_SOURCE")/check-skill-precompute-compose.sh), apply sed -i '30s/-gt 0/-ge 0/' to the copy, and run the test — it exits 0.
Found by a measured mutation run while piloting the detector-findings producer contract (#2680). Filed separately because it is a real defect in this repo's own tooling, independent of that pilot.
The gap
scripts/check-skill-precompute-compose.sh:30:Mutating
-gt 0to-ge 0survives the full suite (scripts/check-skill-precompute-compose.test.sh, baseline green, 5 tests).It survives because every existing test enters through the
-- | --all | --paths)branch, whichbreaks out of the loop before$#reaches 0. Nothing drains arguments through the*)fall-through, so the loop's termination condition is unasserted.Why it matters
The script runs
set -uo pipefail(line 17). With the mutation, once$#reaches 0 the loop body still executes andcase "$1"hits an unbound variable. Measured behavior of the mutant, run from a real checkout:line 31: $1: unbound variable, exit 1--strictline 31: $1: unbound variable, exit 1<base-ref>line 31: $1: unbound variable, exit 1--strict <base-ref>line 31: $1: unbound variable, exit 1--allbreakfires first)So five of six invocation shapes break outright and the suite still reports green. The surviving mutant is not itself interesting; the oracle gap it reveals is — the argument parser has no coverage except through the one branch that short-circuits it.
Suggested fix
Add a case to
scripts/check-skill-precompute-compose.test.shthat invokes the script with a bare base-ref argument (no--all, no--paths) and asserts it reaches the scan rather than erroring. A case passing--strictahead of a base-ref would cover the*)-then-drain path as well.Reproduction
Copy the script and its test into a scratch directory (the test resolves its subject as
$(dirname "$BASH_SOURCE")/check-skill-precompute-compose.sh), applysed -i '30s/-gt 0/-ge 0/'to the copy, and run the test — it exits 0.