The #43 sweep (test/cli.sh:705) matched the literal `-rsp` spelling and
scanned commands/ only. #68 was a plain `read -r reply` in bin/rig, so it
missed on BOTH axes — the spelling and the path — and the bug survived
until a drill hit it.
The class is the shape, not the flags: any `read` run as a plain statement
under `set -euo pipefail` kills the shell at EOF, before the `case` that
would have printed the abort. The failure is silent and exits 1, which is
also what a normal refusal exits, so an exit-code assertion passes against
it.
The new sweep anchors `read` at the start of a statement across bin/ and
commands/, whatever its flags or arity, and subtracts only the two shapes
that are safe by construction: a `||` guard (the cure itself) and a `<<<`
here-string (which cannot return non-zero). `while`/`if ! ` heads need no
subtraction — the anchor already excludes them.
Guard against reintroduction, not a live fix: the tree is clean once #68's
one-token fix lands. Mutation-verified — a bare `read -r foo` planted in
bin/rig gives 404 passed, 1 failed, and the old #43 sweep stays green on
the same tree, which is precisely the gap being closed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
uninstall_confirm() read the operator's answer unguarded:
read -r reply
case "$reply" in y|Y|yes|YES|Yes) return 0 ;; *) die "aborted." ;; esac
bin/rig runs under `set -euo pipefail`, and both call sites (the
single-version and the --all confirms) invoke the function as a plain
statement — nothing suppresses errexit. Ctrl-D makes `read` return
non-zero, so the shell died AT THE READ and the case on the next line
was never evaluated: `die "aborted."` could not fire. The operator saw
the question, pressed Ctrl-D, and got nothing — no message, exit 1, at
exactly the moment the tool had asked whether to delete their install.
It failed closed, so nothing was ever wrongly removed; the damage was
that rig went silent at the one moment silence is unreadable.
The fix is `read -r reply || reply=""` — commands/db.sh:152's spelling
for the identical [y/N] confirm one file away. Empty routes through the
existing `*)` arm, so EOF aborts through the same path a bare Enter
already does: exactly one "aborted." message, no second die to keep in
sync.
test/cli.sh gains the first drills of the interactive path, which was
structurally untested (every existing uninstall check goes through
--force or RIG_YES, which is why this survived): `y` and Ctrl-D driven
through a real pty via util-linux `script`, guarded by a command -v
skip. They assert the MESSAGE, never the exit code — the unfixed code
also exits 1, so an exit-code assertion is green against the bug.
Mutation-verified: with `|| reply=""` reverted, 403 passed / 1 failed,
the single failure being `output missing 'aborted.'`; restored, 404
passed / 0 failed.
Refs #68