From 4ea758fa81e91e5c3db4bf71c72a02e449653e8f Mon Sep 17 00:00:00 2001 From: claude-hdb Date: Mon, 13 Jul 2026 22:05:32 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20three=20first-run=20drill=20defects=20?= =?UTF-8?q?=E2=80=94=20pipefail=20false=20FAILs,=20exec-pty=20hang,=20DHCP?= =?UTF-8?q?=20race?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first live run (issue #15) hit all three: (1) 'set -o pipefail' turned every 'refusal 2>&1 | grep -q' check into a false FAIL — the refusals exit 1/2 by design, and 'grep -q' SIGPIPEs the left side on early match; the pipeline verdict must be grep's alone, so pipefail is gone with a comment explaining why. (2) The in-box background listener inherited the exec pty, so 'incus exec' waited on it forever — the run hung at C7 for 10+ minutes; the listener is now a helper with --- drill/drill.sh | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/drill/drill.sh b/drill/drill.sh index 63325d1..0cb3897 100755 --- a/drill/drill.sh +++ b/drill/drill.sh @@ -25,7 +25,12 @@ # The file is one long 'probe && ok "..." || no "..."'. ok/no always return 0, so # the C-may-run-when-A-is-true trap SC2015 warns about cannot fire here. # shellcheck disable=SC2015 -set -uo pipefail # NOT -e: a failing check is data, not a crash +# +# NOT -e: a failing check is data, not a crash. NOT pipefail: half the checks +# are 'refusal 2>&1 | grep -q text' where the refusal exits 1/2 BY DESIGN, and +# 'grep -q' SIGPIPEs the left side on early match — pipefail turned both into +# false FAILs on the first live run. The pipeline verdict must be grep's alone. +set -u REPO="${CLAUDEBOX_REPO:-heavy-duty/claudebox}" REF="${CLAUDEBOX_REF:-main}" @@ -62,9 +67,25 @@ wait_box() { # poll until exec answers (the VM agent can take a while), ~2 min } eth0_ip() { # the box's address on claudenet — eth0 exactly; a box running - # docker reports several addresses across quoted CSV lines - incus list "^$1\$" --format csv --columns 4 | tr -d '"' | tr ',' '\n' \ - | awk '/\(eth0\)$/ { print $1; exit }' + # docker reports several addresses across quoted CSV lines. + # Retries: the agent answers before DHCP hands out the address. + local b="$1" ip _i + for _i in $(seq 1 15); do + ip="$(incus list "^$b\$" --format csv --columns 4 | tr -d '"' | tr ',' '\n' \ + | awk '/\(eth0\)$/ { print $1; exit }')" + [ -n "$ip" ] && { printf '%s\n' "$ip"; return 0; } + sleep 2 + done + return 1 +} + +box_listen() { # start a throwaway HTTP listener INSIDE a box, detached. + # /dev/null && { nohup python3 -m http.server $2 --bind 0.0.0.0 >/dev/null 2>&1 /dev/null 2>&1 + sleep 1 } # --- stage 1: consent, install, then re-enter inside the incus-admin group --- @@ -233,8 +254,16 @@ fi claudebox info drill | grep -q '^IPV4' && ok "info shows an IPv4" || no "info has no IPV4 row" claudebox info drill | grep -q 'SNAPSHOTS (none)' && ok "info: no snapshots yet, offers to take one" || no "info snapshot-empty state wrong" -claudebox exec drill -- claude --version >/dev/null 2>&1 \ - && ok "Claude Code is installed in the box" || no "'claude --version' failed inside the box" +if claudebox exec drill -- claude --version >/dev/null 2>&1; then + ok "Claude Code is installed in the box" +elif timeout 30 claudebox exec drill -- bash -lc 'claude --version' >/dev/null 2>&1; then + no "'claude' is installed but NOT on exec's PATH — repo bug: the help promises 'claudebox exec work -- claude --version'" + inf "PATH as exec sees it: $(timeout 30 claudebox exec drill -- printenv PATH 2>/dev/null)" +else + no "'claude --version' failed inside the box" + inf "cloud-init: $(timeout 30 claudebox incus drill -- exec {} -- cloud-init status 2>&1 | head -1)" + inf "claude's ~/.local/bin holds: $(timeout 30 claudebox incus drill -- exec {} -- ls /home/claude/.local/bin 2>&1 | tr '\n' ' ' | cut -c1-120)" +fi claudebox exec drill -- gh --version >/dev/null 2>&1 \ && ok "the GitHub CLI is installed in the box (PR #5)" || no "'gh --version' failed inside the box" @@ -327,7 +356,7 @@ claudebox exec archive -- curl -sS -m 5 -o /dev/null http://192.168.1.1 2>/dev/n # egress drop is not covering siblings) · timeout = dropped, as designed. PEER_IP="$(eth0_ip peer)" if [ -n "$PEER_IP" ]; then - claudebox exec peer -- bash -c 'command -v python3 >/dev/null && nohup python3 -m http.server 8088 --bind 0.0.0.0 >/dev/null 2>&1 & sleep 1' >/dev/null 2>&1 + box_listen peer 8088 claudebox exec archive -- curl -sS -m 5 -o /dev/null "http://$PEER_IP:8088" 2>/dev/null rc=$? case "$rc" in @@ -362,7 +391,7 @@ fi # C7 — inbound, host → box (#15 A7): the ACL's default ingress drop ARCH_IP="$(eth0_ip archive)" -claudebox exec archive -- bash -c 'command -v python3 >/dev/null && nohup python3 -m http.server 8087 --bind 0.0.0.0 >/dev/null 2>&1 & sleep 1' >/dev/null 2>&1 +box_listen archive 8087 if [ -n "$ARCH_IP" ] && curl -sS -m 5 -o /dev/null "http://$ARCH_IP:8087" 2>/dev/null; then no "the HOST connected to a listener inside the box — the default ingress drop is not holding" aud "A7 inbound host→box: FAIL — reached a box listener" -- 2.45.2