confirm() swallows Ctrl-D: an interactive abort exits 1 in silence, never reaching 'aborted.' #111
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#111
Loading…
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?
Surfaced by
claude-bot-andresmgslduring review of #109, as an explicitly non-blocking note. Filing it so it is not lost with the PR.The defect
confirm()atbin/box:826reads the operator's answer unguarded:Ctrl-D at a real interactive prompt makes
readreturn non-zero. Underset -euo pipefailthe run ends silently with exit 1 — thecaseis never reached, sodie "aborted."never fires. The operator sees the question, presses Ctrl-D, and gets nothing: no confirmation of the abort, no message, no explanation of the exit code.uninstall_confirm()atbin/box:1654has the identical shape.This is rig#43, again
heavy-duty/rig#43already diagnosed and cured exactly this pattern withread ... || die. box has the same gap in the same construct. Worth pointing at that issue for the reasoning rather than re-deriving it here.Severity: low, and honest about why
It fails closed — nothing is destroyed, the abort is real. The damage is purely that the tool goes silent at the exact moment it asked the operator a question, which is the worst moment to say nothing. An operator who Ctrl-Ds out of a
box rmprompt cannot tell from the output whether the box survived.It also predates #109 —
rmhas always had it, andbox rmhas carried a confirm prompt for as long as the verb has existed. This is not a regression.Why file it now rather than let it ride
#109 takes the number of verbs reaching that line from one to two (
rm, and nowrestore). The exposure doubled, and both verbs are irreversible. That is the argument for closing it now rather than the next time someone notices.It was deliberately kept out of #109 — a different defect with a different blast radius, and folding an unrelated pre-existing fix into a destructive-gate PR would have muddied a diff that reviewers were checking line by line.
The fix
One token in two places:
bin/box:826—read -r reply || die "aborted."bin/box:1654— same, inuninstall_confirm()Mirror rig#43's cure so the two repos read alike.
Testing note, stated honestly
The interactive accept (
y) and abort (n) paths are currently untested, structurally — they need a pty, which the fake-incus harness does not provide.test/cli.shcovers the--forcebypass and the no-TTY refusal (both driven, both green) but cannot reach the branch this issue is about.So the EOF path is testable only through a
script-based check. That is worth building — it would close the whole family at once rather than this one line — but it is a bigger lift than the fix, and the fix should not wait for it. If the pty harness is out of scope, say so in the PR rather than claiming coverage that is not there.Refs
#109 (where it was found; the review comment has the full analysis) · #105 (the gate that doubled the exposure) ·
heavy-duty/rig#43(the same defect, already cured there)