diff --git a/sandy b/sandy index ad1961c..7a83db7 100755 --- a/sandy +++ b/sandy @@ -1040,7 +1040,14 @@ _sandy_emit_container_daemon_fields() { # non-zero status hits the introspection ERR trap under set -eE and # kills the whole emission (host regression: one broken daemon # container failed every --print-state consumer). - _ac="$(docker exec -u "$(id -u)" "$_cid" tmux display -p -t sandy '#{session_attached}' 2>/dev/null)" || _ac="" + # `|| true` INSIDE the substitution, not `|| _ac=""` outside it: with + # `set -E` the introspection ERR trap is inherited by the subshell, and + # bash 3.2 (macOS /bin/bash) fires it for the failing `docker exec` even + # though the outer `||` handles the status — printing a spurious + # "introspection handler failed / Report this at ..." on every + # --print-state with an unreachable probe. bash 4+ exempts it; macOS does + # not. Absorbing the failure inside the subshell silences it on both. + _ac="$(docker exec -u "$(id -u)" "$_cid" tmux display -p -t sandy '#{session_attached}' 2>/dev/null || true)" if ! [[ "$_ac" =~ ^[0-9]+$ ]]; then _ac="null" fi diff --git a/test/run-tests.sh b/test/run-tests.sh index 1450cbe..523beac 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -5124,7 +5124,15 @@ _df_assert() { # $1=mode ("" or "light") local _json _rc=0 _json="$(PATH="$_DF_BIN:$PATH" bash "$_SBX_SCRIPT" --print-state ${1:+$1} 2>/dev/null)" || _rc=$? [ "$_rc" -eq 0 ] || return 1 - python3 -c " + # QUOTED heredoc, NOT `python3 -c "..."`: in a double-quoted string bash + # expands backticks/$( ) inside what looks like a Python comment. The word + # `sandy` in the regression note below was command substitution — on a host + # with sandy on PATH the suite RAN it and spliced its multi-line output into + # this script, corrupting it into a SyntaxError and failing both checks. + # Invisible on CI (no sandy on PATH -> empty expansion). Same class as the + # Dockerfile.proxy backtick bug guarded above. `python3 - ARG` keeps + # sys.argv[1] == the JSON, so the assertions are unchanged. + python3 - "$_json" <<'DFPY' import json, sys d = json.loads(sys.argv[1]) rc = d['running_containers'] @@ -5143,7 +5151,7 @@ assert isinstance(daemon['attached_clients'], int), daemon assert broken['daemon'] is True, broken assert broken['attached_clients'] is None, broken assert bare['attached_clients'] is None, bare -# Regression: the proxy of a `sandy`-named workspace must join to sandy-92aa9f98, +# Regression: the proxy of a "sandy"-named workspace must join to sandy-92aa9f98, # NOT the double-stripped bare hash '92aa9f98'. A proxy is not a daemon session. assert proxy['sandbox'] == 'sandy-92aa9f98', proxy assert proxy['daemon'] is False, proxy @@ -5154,7 +5162,7 @@ assert proxy['attached_clients'] is None, proxy assert daemon['updated_at'] == '2026-07-14T12:00:00Z', daemon assert broken['updated_at'] is None, broken assert bare['updated_at'] is None, bare -" "$_json" +DFPY } check "full mode: daemon fields correct; a failing attach-probe yields null, not a dead emission" \ _df_assert @@ -6678,7 +6686,14 @@ check "session-end notice compares HEAD (symbolic-ref) + surfaces the change" \ check ".head-at-launch is cleaned up unconditionally at session end" \ bash -c 'grep -q "rm -f .*\.head-at-launch" "$1"' -- "$_S83" # _sandy_head_display behavioral unit test (pure fn, no docker) -_hd_out="$(bash -c 'source <(sed -n "/^_sandy_head_display()/,/^}/p" "$1"); _sandy_head_display refs/heads/feature/x; printf "|"; _sandy_head_display detached:abc1234' -- "$_S83")" +# Extract-then-eval, NOT `source <(sed ...)` inside `$(...)`: bash 3.2 (macOS) +# does not reliably handle nested process substitution there — it yields an +# empty source and `command not found` (exit 127), which under `set -euo +# pipefail` aborts the whole suite. Same trap already called out at the top of +# this file; CI (Ubuntu/bash 5) cannot catch it. +_hd_fn="$(sed -n '/^_sandy_head_display()/,/^}/p' "$_S83")" +_hd_out="$(bash -c "$_hd_fn +_sandy_head_display refs/heads/feature/x; printf '|'; _sandy_head_display detached:abc1234")" check "_sandy_head_display: refs/heads/; detached:→detached HEAD ()" \ test "$_hd_out" = "feature/x|detached HEAD (abc1234)"