Stop setup blocking on invisible sbx confirmation prompts

sbx skills import prompts before overwriting each skill already in the shared
store. setup called it with stdout and stderr redirected and stdin left
attached, so on any second run the prompt was invisible and setup hung
indefinitely partway through installing the Claude configuration.

sbx rm prompts the same way, which would have blocked --replace and remove
once a sandbox was in use.

Both now pass --force and read from /dev/null. This is the third instance of
the pattern after sbx secret set, which cancelled silently and still exited 0,
so a test now asserts at the source level that every prompting subcommand is
invoked non-interactively. The test was confirmed to fail when either --force
is removed.
This commit is contained in:
2026-07-31 09:04:21 -05:00
parent 5e167d3a0d
commit 6f4ba117b4
2 changed files with 51 additions and 3 deletions
+6 -3
View File
@@ -660,7 +660,10 @@ install_sandbox_claude_config() {
# The store is shared by every sandbox, so this seeds all of them at once. # The store is shared by every sandbox, so this seeds all of them at once.
if [[ -d "$CLAUDE_HOME/skills" ]]; then if [[ -d "$CLAUDE_HOME/skills" ]]; then
sbx skills import >/dev/null 2>&1 || # --force is mandatory: the store is shared, so a second setup finds
# skills already there and prompts per skill. With output redirected
# the prompt is invisible and setup hangs on stdin forever.
sbx skills import --force </dev/null >/dev/null 2>&1 ||
printf 'Could not import skills into the shared store.\n' >&2 printf 'Could not import skills into the shared store.\n' >&2
fi fi
@@ -909,7 +912,7 @@ setup_command() {
if sandbox_exists; then if sandbox_exists; then
if [[ "$replace" == true ]]; then if [[ "$replace" == true ]]; then
printf 'Removing existing sandbox %s...\n' "$SANDBOX_NAME" printf 'Removing existing sandbox %s...\n' "$SANDBOX_NAME"
sbx rm "$SANDBOX_NAME" sbx rm --force "$SANDBOX_NAME" </dev/null
else else
printf 'Using existing sandbox %s.\n' "$SANDBOX_NAME" printf 'Using existing sandbox %s.\n' "$SANDBOX_NAME"
fi fi
@@ -1042,7 +1045,7 @@ remove_command() {
load_config load_config
if sandbox_exists; then if sandbox_exists; then
sbx rm "$SANDBOX_NAME" sbx rm --force "$SANDBOX_NAME" </dev/null
fi fi
rm -rf "$REPO_CONFIG_DIR" rm -rf "$REPO_CONFIG_DIR"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
TASK="$(dirname "${BASH_SOURCE[0]}")/../tasks/ai/sbx"
failures=0
fail() {
printf 'FAIL: %s\n' "$1" >&2
failures=$((failures + 1))
}
code() {
grep -n '^[^#]*sbx ' "$TASK" | grep -v 'ai:sbx'
}
require_force() {
local pattern="$1" line
local found=false
while IFS= read -r line; do
found=true
[[ "$line" == *"--force"* ]] ||
fail "missing --force, will block on a prompt: ${line#*:}"
done < <(code | grep -F "$pattern" || true)
[[ "$found" == true ]] ||
fail "no invocation of '$pattern' found; has it been renamed?"
}
require_force 'sbx secret set'
require_force 'sbx skills import'
require_force 'sbx rm '
while IFS= read -r line; do
[[ "$line" == *"</dev/null"* ]] ||
fail "missing </dev/null: ${line#*:}"
done < <(code | grep -F 'sbx skills import' || true)
if ((failures)); then
printf '%d assertion(s) failed\n' "$failures" >&2
exit 1
fi
printf 'All non-interactive invocation assertions passed.\n'