test: widen the read-guard sweep to bin/ and to plain-statement reads

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>
This commit is contained in:
dan-claude-bot 2026-07-20 09:58:19 +00:00
parent 880e95df21
commit d144ae379c

View file

@ -817,6 +817,30 @@ check "runner remove: headless token prompt refuses loudly" 0 "" \
# so a bare `read -rsp` (no `||` on its line) must not exist anywhere.
check "prompts: no bare read -rsp remains" 1 "" \
grep -RE 'read -rsp[^|]*$' "$ROOT/commands/"
# ...and the same sweep, widened on the two axes #68 escaped through (#75).
# That check reads `-rsp` literally and scans commands/ only; #68 was a plain
# `read -r reply` in bin/rig, so it missed on the spelling AND on the path.
# 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 — silently, with exit 1, indistinguishable
# from a normal refusal.
#
# So: match `read` at the start of a statement (leading whitespace only),
# whatever its flags or arity, then subtract the two shapes that are safe by
# construction:
# `||` — the guard itself (`|| die`, `|| reply=""`, `|| { echo; die … }`).
# An errexit-exempt read, which is the whole cure.
# `<<<` — a here-string always supplies a terminating newline, so the read
# cannot return non-zero. lib/users-config.sh:50/:78 are these.
# `while`/`until`/`if` heads need no subtraction: the anchor already excludes
# them, since `read` is not the first word on those lines. Keep the guard on
# the read's own line — a `\`-continued `||` reads as unguarded here, by
# design, because it is not visible at the point of failure.
unguarded_read() {
grep -REn '^[[:space:]]*read[[:space:]]' "$ROOT/bin/" "$ROOT/commands/" \
| grep -Ev '\|\||<<<'
}
check "prompts: no unguarded plain-statement read remains (#75)" 1 "" unguarded_read
# --- runner install: --repo must agree with what the box is already on -------
# The bug: `install --repo B` on a box registered to repo A skipped configure,