Interactive prompts die silently when stdin is not a tty — exit 1, zero output #42
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
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/rig#42
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?
Found by the 2026-07-19 release drill (real host, real guests, real GitHub runner).
What happened
Three commands prompt for a credential with a bare
read -rspwhen their env var is unset:commands/bootstrap.sh:401—TS_AUTHKEYcommands/runner-install.sh:124—RUNNER_TOKENcommands/runner-remove.sh:74—RUNNER_REMOVE_TOKENWhen stdin is not a tty (CI,
box exec/incus execwithout-t, any scripted invocation),readreturns non-zero immediately,set -euo pipefailkills the script, and nothing is printed at all — not even which variable was missing.Measured twice in the drill:
sudo RUNNER_TOKEN=… rig runner remove(wrong var on purpose) → exit 1, zero output. The operator's only clue is the exit code.incus exec <guest> -- rig bootstrap workload --hostname wl-rehearsal(noTS_AUTHKEY) → converges packages/sshd/hostname, then dies at the join step: exit 1, no error line. The last thing printed isrig-bootstrap: system hostname already wl-rehearsal, which reads like success until you check$?.Why it matters
A silent exit 1 in the middle of a bootstrap is the worst kind of failure for a convergence tool: the box is half-converged and the log ends mid-sentence. Every other refusal in rig names what it wants (
die "--version <pin> is required").The fix pattern is already in the repo
commands/db.sh:152guards the same situation:read -r reply || reply="".install.shconfirm()is the family precedent: it checks for a tty and dies loudly namingBOX_YESas the non-interactive path.Suggested shape: before each prompt,
[ -t 0 ] || die "stdin is not a tty: set <VAR> (see --help)"— or theread … || die "…"equivalent — so headless invocations fail loudly naming the variable, exactly like every other refusal.🤖 Filed from the release-drill session.