Interactive prompts die silently when stdin is not a tty — exit 1, zero output #42

Closed
opened 2026-07-19 12:09:39 +00:00 by dan-claude-bot · 0 comments
dan-claude-bot commented 2026-07-19 12:09:39 +00:00 (Migrated from github.com)

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 -rsp when their env var is unset:

  • commands/bootstrap.sh:401TS_AUTHKEY
  • commands/runner-install.sh:124RUNNER_TOKEN
  • commands/runner-remove.sh:74RUNNER_REMOVE_TOKEN

When stdin is not a tty (CI, box exec/incus exec without -t, any scripted invocation), read returns non-zero immediately, set -euo pipefail kills the script, and nothing is printed at all — not even which variable was missing.

Measured twice in the drill:

  1. sudo RUNNER_TOKEN=… rig runner remove (wrong var on purpose) → exit 1, zero output. The operator's only clue is the exit code.
  2. incus exec <guest> -- rig bootstrap workload --hostname wl-rehearsal (no TS_AUTHKEY) → converges packages/sshd/hostname, then dies at the join step: exit 1, no error line. The last thing printed is rig-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:152 guards the same situation: read -r reply || reply="".
  • box's install.sh confirm() is the family precedent: it checks for a tty and dies loudly naming BOX_YES as the non-interactive path.

Suggested shape: before each prompt, [ -t 0 ] || die "stdin is not a tty: set <VAR> (see --help)" — or the read … || die "…" equivalent — so headless invocations fail loudly naming the variable, exactly like every other refusal.

🤖 Filed from the release-drill session.

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 -rsp` when their env var is unset: - `commands/bootstrap.sh:401` — `TS_AUTHKEY` - `commands/runner-install.sh:124` — `RUNNER_TOKEN` - `commands/runner-remove.sh:74` — `RUNNER_REMOVE_TOKEN` When stdin is **not a tty** (CI, `box exec`/`incus exec` without `-t`, any scripted invocation), `read` returns non-zero immediately, `set -euo pipefail` kills the script, and **nothing is printed at all** — not even which variable was missing. Measured twice in the drill: 1. `sudo RUNNER_TOKEN=… rig runner remove` (wrong var on purpose) → exit 1, **zero output**. The operator's only clue is the exit code. 2. `incus exec <guest> -- rig bootstrap workload --hostname wl-rehearsal` (no `TS_AUTHKEY`) → converges packages/sshd/hostname, then dies at the join step: exit 1, **no error line**. The last thing printed is `rig-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:152` guards the same situation: `read -r reply || reply=""`. - box's `install.sh` `confirm()` is the family precedent: it checks for a tty and dies loudly naming `BOX_YES` as the non-interactive path. Suggested shape: before each prompt, `[ -t 0 ] || die "stdin is not a tty: set <VAR> (see --help)"` — or the `read … || die "…"` equivalent — so headless invocations fail loudly naming the variable, exactly like every other refusal. 🤖 Filed from the release-drill session.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/rig#42
No description provided.