From 60f60cd02a8fdd67b1cb938e2ed7d08b5dbd62d0 Mon Sep 17 00:00:00 2001 From: marcie Date: Sat, 29 Aug 2026 11:37:13 -0500 Subject: [PATCH 1/2] framework tools/tmux: B1 socket-resolution series (PR #1466, rebased onto next) Content-identical replay of the reviewed B1 series (codex APPROVE at 0026c0f9; rounds 1-7 authored on the p1-wrappers line whose #1464 squash could not carry the tmux files). Delta vs fork 34e56f24: agent-send.sh +66/-4 (socket default resolution: explicit -L > MOSAIC_TMUX_SOCKET > unique hit > ambiguity refusal rc 4; exact session matching; local-only env), test-send-message-socket.sh +87 (discovery + sender arms). Reviews at 0026c0f9 went stale on the head move; re-review routed via orch-01. --- .../mosaic/framework/tools/tmux/agent-send.sh | 66 +++++++++++++- .../tools/tmux/test-send-message-socket.sh | 87 ++++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/packages/mosaic/framework/tools/tmux/agent-send.sh b/packages/mosaic/framework/tools/tmux/agent-send.sh index d44e46ca..3c16555b 100755 --- a/packages/mosaic/framework/tools/tmux/agent-send.sh +++ b/packages/mosaic/framework/tools/tmux/agent-send.sh @@ -35,6 +35,9 @@ # # OPTIONS # -L NAME tmux socket name passed to `tmux -L NAME` on the target host +# +# Exit 4: local target session exists on multiple socket servers and no +# -L / MOSAIC_TMUX_SOCKET disambiguated it (B1 stale-twin guard). # -s DST_SESSION target tmux session (or session:window.pane) [required] # -H SSH_TARGET ssh target (user@host) for a remote pane; omit for local # -n DST_HOST hostname to show in the preamble for the target. @@ -63,8 +66,10 @@ # group 1 = src label group 2 = dst host:session # group 3 = class (absent => actionable) group 4 = message body # -# EXIT CODES (passed through from send-message.sh) +# EXIT CODES (passed through from send-message.sh, except 4) # 0 delivered/queued · 1 target not found · 2 still draft · 3 usage error +# 4 agent-send refusal: local target session exists on multiple socket +# servers and no -L / MOSAIC_TMUX_SOCKET disambiguated it (B1) set -uo pipefail SELF_DIR=$(cd -- "$(dirname -- "$0")" && pwd) @@ -154,6 +159,61 @@ FULL="${PREAMBLE} ${MSG}" B64=$(printf '%s' "$FULL" | base64 -w0) vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v" + +# Exact session matching for the sender target (codex PR #1466): without +# '=', tmux target syntax accepts an unambiguous PREFIX, so a delivery +# aimed at session X can land in X-old. Compound targets (session:win.pane) +# and already-exact ('=...') forms pass through untouched. Computed BEFORE +# socket discovery so the discovery probes use the same target semantics +# (probing '==name' for an already-exact input was a false-negative hit). +DST_TARGET="$DST_SESSION" +case "$DST_SESSION" in + =*) ;; + *:*) + # Compound target (session:win.pane): pin the SESSION component exact + # (=session:win.pane); unpinned, the session part still prefix-matches + # (codex PR #1466: 'agent:0.0' can resolve into 'agent-old'). + DST_TARGET="=${DST_SESSION%%:*}:${DST_SESSION#*:}" + ;; + *) DST_TARGET="=$DST_SESSION" ;; +esac + +# Socket default resolution (B1, 2026-08-29). Precedence: explicit -L > +# launcher-exported MOSAIC_TMUX_SOCKET > unique socket hit > refusal on +# ambiguity > tmux default socket. The ambiguity refusal fires ONLY when +# no explicit or env choice exists and the session name lives on multiple +# servers (measured 2026-08-28/29: tasking sends landed in a stale +# default-socket twin; rc 0 reported honest delivery to the wrong pane). +# Socket discovery scans tmux's own socket dir, ${TMUX_TMPDIR:-/tmp}/tmux-UID +# (codex PR #1466: TMPDIR is not where tmux keeps -L sockets). +# MOSAIC_TMUX_SOCKET is LOCAL-host state (launcher-exported): it must not +# leak into remote sends, where -L would target a socket on the remote +# host (codex PR #1466). +if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ] && [ -n "${MOSAIC_TMUX_SOCKET:-}" ]; then + SOCKET_NAME="$MOSAIC_TMUX_SOCKET" +fi +if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ]; then + socket_dir="${TMUX_TMPDIR:-/tmp}/tmux-$(id -u)" + hits="" + for sf in "$socket_dir"/*; do + [ -S "$sf" ] || continue + sname="${sf##*/}" + # '=' forces exact session-name matching: tmux target syntax otherwise + # accepts an unambiguous PREFIX, so a session named X-old on a socket + # would count as a false hit for target X (codex PR #1466). + tmux -L "$sname" has-session -t "$DST_TARGET" 2>/dev/null && hits="$hits$sname"$'\n' + done + hit_count=$(printf '%s' "$hits" | grep -c . || true) + if [ "$hit_count" -gt 1 ]; then + echo "agent-send.sh: REFUSING - session '$DST_SESSION' exists on multiple sockets:" >&2 + printf ' %s\n' $hits >&2 + echo " Pass -L explicitly (or export MOSAIC_TMUX_SOCKET to disambiguate)." >&2 + exit 4 + elif [ "$hit_count" -eq 1 ]; then + SOCKET_NAME="$(printf '%s' "$hits")" + fi +fi + socket_args=() if [ -n "$SOCKET_NAME" ]; then socket_args=(-L "$SOCKET_NAME") @@ -161,9 +221,9 @@ fi if [ -z "$SSH_TARGET" ]; then # Local pane: call the canonical sender directly. - exec "$SENDER" "${socket_args[@]}" -t "$DST_SESSION" -b "$B64" -r "$RETRIES" $vflag + exec "$SENDER" "${socket_args[@]}" -t "$DST_TARGET" -b "$B64" -r "$RETRIES" $vflag else # Remote pane: ship the sender over ssh and run it local to the target. ssh -o ConnectTimeout=10 "$SSH_TARGET" \ - "bash -s -- ${socket_args[*]@Q} -t '$DST_SESSION' -b '$B64' -r '$RETRIES' $vflag" < "$SENDER" + "bash -s -- ${socket_args[*]@Q} -t '$DST_TARGET' -b '$B64' -r '$RETRIES' $vflag" < "$SENDER" fi diff --git a/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh b/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh index 8f2d90bb..0d2ef2fe 100755 --- a/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh +++ b/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh @@ -8,7 +8,15 @@ SOCKET="mosaic-test-$RANDOM-$$" TARGET="target-$RANDOM" DEFAULT_TARGET="default-target-$RANDOM" TMPDIR=$(mktemp -d) -trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; tmux kill-session -t "$DEFAULT_TARGET" >/dev/null 2>&1 || true; rm -rf "$TMPDIR"' EXIT +ART_OUT=$(mktemp) +AMB_OUT=$(mktemp) +AMB_ERR=$(mktemp) +A2_OUT=$(mktemp) +A2_ERR=$(mktemp) +UNIQ_OUT=$(mktemp) +UNIQ_ERR=$(mktemp) +TWIN="twin-$RANDOM-$$" +trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; tmux kill-session -t "$DEFAULT_TARGET" >/dev/null 2>&1 || true; tmux kill-session -t "$TWIN" >/dev/null 2>&1 || true; rm -rf "$TMPDIR" $ART_OUT $AMB_OUT $AMB_ERR $A2_OUT $A2_ERR $UNIQ_OUT $UNIQ_ERR' EXIT fail() { echo "FAIL: $*" >&2 @@ -79,4 +87,81 @@ for i in $(seq 1 "$CONC_N"); do done done +# B1 (2026-08-29): socket default resolution in agent-send.sh. Measured +# defect: tasking sends without -L landed in a stale default-socket twin of +# the target seat; rc 0 reported honest delivery to the wrong pane. + +# Arm A: session on MULTIPLE sockets, no -L -> refuse with rc 4 naming both. +tmux -L "$SOCKET" new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +tmux new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +amb_rc=0 +env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN" -m "must refuse" >$AMB_OUT 2>$AMB_ERR || amb_rc=$? +[ "$amb_rc" -eq 4 ] || fail "ambiguity refusal: rc=$amb_rc want 4 (stderr: $(cat $AMB_ERR))" +grep -q "multiple sockets" $AMB_ERR || fail "ambiguity refusal message missing socket list" +grep -qF "$SOCKET" $AMB_ERR || fail "ambiguity refusal message does not name the test socket" +tmux kill-session -t "$TWIN" >/dev/null 2>&1 || true +tmux -L "$SOCKET" kill-session -t "$TWIN" >/dev/null 2>&1 || true + +# Arm A2: with MOSAIC_TMUX_SOCKET exported, a twin session is NOT ambiguous: +# the env var disambiguates by precedence (codex PR #1466 blocker). +tmux -L "$SOCKET" new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +tmux new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +a2_rc=0 +MOSAIC_TMUX_SOCKET="$SOCKET" "$AGENT_SEND" -s "$TWIN" -m "env disambiguated" >$A2_OUT 2>$A2_ERR || a2_rc=$? +[ "$a2_rc" -eq 0 ] || fail "env disambiguation: rc=$a2_rc (stderr: $(cat $A2_ERR))" +sleep 0.2 +a2_pane="$(tmux -L "$SOCKET" capture-pane -t "=$TWIN:0.0" -p)" || fail "cannot capture twin (arm A2)" +grep -qF "env disambiguated" <<<"$a2_pane" || fail "env disambiguation did not deliver on the named socket" +a2_default="$(tmux capture-pane -t "=$TWIN:0.0" -p)" || true +if grep -qF "env disambiguated" <<<"$a2_default"; then + fail "env disambiguation cross-delivered to the default-socket twin" +fi +tmux kill-session -t "$TWIN" >/dev/null 2>&1 || true +tmux -L "$SOCKET" kill-session -t "$TWIN" >/dev/null 2>&1 || true + +# Arm B: session unique to ONE socket, no -L -> auto-resolve to that socket +# and deliver there. +# Arm A3: prefix matching must not produce false socket hits (codex PR +# #1466): a session named TWIN-old must not count as a hit for target +# TWIN (tmux target syntax prefix-matches without '='). +PSEUDO="${TWIN}-old" +tmux new-session -d -s "$PSEUDO" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +A3_ERR=$(mktemp) +a3_rc=0 +env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN" -m "prefix trap" >/dev/null 2>"$A3_ERR" || a3_rc=$? +# TWIN exists nowhere (both twins killed after arm A2); with '=' the +# PSEUDO session is not a hit, so the sender must fail target-not-found +# (rc 1) instead of delivering into the prefix-named session. +[ "$a3_rc" -eq 1 ] || fail "prefix false-hit: rc=$a3_rc want 1 (stderr: $(cat "$A3_ERR"))" +if tmux capture-pane -t "=$PSEUDO:0.0" -p 2>/dev/null | grep -qF "prefix trap"; then + fail "delivery landed in the prefix-named session (false socket hit)" +fi +tmux kill-session -t "$PSEUDO" >/dev/null 2>&1 || true +rm -f "$A3_ERR" + +# Arm A4: compound targets pin the SESSION component exact (codex PR +# #1466): 'TWIN:0.0' must not resolve into the prefix-named session. +PSEUDO2="${TWIN}-old" +tmux new-session -d -s "$PSEUDO2" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +A4_ERR=$(mktemp) +a4_rc=0 +env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN:0.0" -m "compound trap" >/dev/null 2>"$A4_ERR" || a4_rc=$? +[ "$a4_rc" -eq 1 ] || fail "compound prefix false-hit: rc=$a4_rc want 1 (stderr: $(cat "$A4_ERR"))" +if tmux capture-pane -t "=$PSEUDO2:0.0" -p 2>/dev/null | grep -qF "compound trap"; then + fail "compound delivery landed in the prefix-named session" +fi +tmux kill-session -t "$PSEUDO2" >/dev/null 2>&1 || true +rm -f "$A4_ERR" + +uniq_rc=0 +env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TARGET" -m "autoresolved hello" >$UNIQ_OUT 2>$UNIQ_ERR || uniq_rc=$? +[ "$uniq_rc" -eq 0 ] || fail "unique auto-resolution: rc=$uniq_rc (stderr: $(cat $UNIQ_ERR))" +sleep 0.2 +auto_pane="$(capture_named)" || fail "could not capture named socket pane (arm B)" +grep -qF "autoresolved hello" <<<"$auto_pane" || fail "auto-resolution did not deliver to the named-socket pane" +default_pane2="$(capture_default)" || fail "could not capture default socket pane (arm B)" +if grep -qF "autoresolved hello" <<<"$default_pane2"; then + fail "auto-resolution cross-delivered to the default socket pane" +fi + echo "ok - named tmux socket send tools" -- 2.54.0 From 2e86e66681041a66de1bd4cf746e969b75e6985f Mon Sep 17 00:00:00 2001 From: marcie Date: Sat, 29 Aug 2026 13:53:17 -0500 Subject: [PATCH 2/2] B1 suite: no early-exiting consumers in pipes (pipefail gate) 2987 serial CI caught what the batch runs obscured: the repo's pipefail-early-exit static gate flags tmux-capture-pane piped into grep -qF (early-exiting consumer = SIGPIPE hazard under pipefail). A3/A4 assertion arms rewritten: capture the pane to a variable (failure-tolerant), then full-consumption grep -F >/dev/null (no -q). Gate green locally (node --test scripts/pipefail-early-exit.test.mjs fail 0); full suite parity with clean-next baseline (only the 4 node-25 localStorage web host artifacts fail, identical set). --- .../mosaic/framework/tools/tmux/test-send-message-socket.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh b/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh index 0d2ef2fe..68b7a097 100755 --- a/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh +++ b/packages/mosaic/framework/tools/tmux/test-send-message-socket.sh @@ -133,7 +133,8 @@ env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN" -m "prefix trap" >/dev/null 2 # PSEUDO session is not a hit, so the sender must fail target-not-found # (rc 1) instead of delivering into the prefix-named session. [ "$a3_rc" -eq 1 ] || fail "prefix false-hit: rc=$a3_rc want 1 (stderr: $(cat "$A3_ERR"))" -if tmux capture-pane -t "=$PSEUDO:0.0" -p 2>/dev/null | grep -qF "prefix trap"; then +a3_pane="$(tmux capture-pane -t "=$PSEUDO:0.0" -p 2>/dev/null)" || a3_pane="" +if printf '%s' "$a3_pane" | grep -F "prefix trap" >/dev/null; then fail "delivery landed in the prefix-named session (false socket hit)" fi tmux kill-session -t "$PSEUDO" >/dev/null 2>&1 || true @@ -147,7 +148,8 @@ A4_ERR=$(mktemp) a4_rc=0 env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN:0.0" -m "compound trap" >/dev/null 2>"$A4_ERR" || a4_rc=$? [ "$a4_rc" -eq 1 ] || fail "compound prefix false-hit: rc=$a4_rc want 1 (stderr: $(cat "$A4_ERR"))" -if tmux capture-pane -t "=$PSEUDO2:0.0" -p 2>/dev/null | grep -qF "compound trap"; then +a4_pane="$(tmux capture-pane -t "=$PSEUDO2:0.0" -p 2>/dev/null)" || a4_pane="" +if printf '%s' "$a4_pane" | grep -F "compound trap" >/dev/null; then fail "compound delivery landed in the prefix-named session" fi tmux kill-session -t "$PSEUDO2" >/dev/null 2>&1 || true -- 2.54.0