From 2adfaf18120d6ad6be90664d69d03c45f6d5530d Mon Sep 17 00:00:00 2001 From: marcie Date: Fri, 28 Aug 2026 20:41:36 -0500 Subject: [PATCH] framework tools/tmux: agent-send socket default resolution + ambiguity guard (B1) Measured defect (2026-08-28/29): tasking sends without -L landed in a stale default-socket TWIN of the target seat; rc 0 reported honest delivery to the wrong pane. Resolution order now: explicit -L > MOSAIC_TMUX_SOCKET (launcher-exported) > unique socket hit; multiple hits refuse with rc 4 naming the sockets. Local targets only; remote pass-through unchanged. Socket test gains arms A (twin session on two sockets -> rc 4 with the socket list) and B (unique session auto-resolves and delivers on the right socket, no cross-delivery). Test remains CI-excluded per its signed exclusion (no tmux in the CI image, #1017). --- .../mosaic/framework/tools/tmux/agent-send.sh | 31 +++++++++++++++++++ .../tools/tmux/test-send-message-socket.sh | 31 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/mosaic/framework/tools/tmux/agent-send.sh b/packages/mosaic/framework/tools/tmux/agent-send.sh index d44e46ca..675f4d63 100755 --- a/packages/mosaic/framework/tools/tmux/agent-send.sh +++ b/packages/mosaic/framework/tools/tmux/agent-send.sh @@ -154,6 +154,37 @@ FULL="${PREAMBLE} ${MSG}" B64=$(printf '%s' "$FULL" | base64 -w0) vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v" + +# Socket default resolution (B1, 2026-08-29): explicit -L wins; else the +# launcher-exported MOSAIC_TMUX_SOCKET; else the tmux default socket. When +# the target session exists on MULTIPLE socket servers and no explicit -L +# was given, refuse with a disambiguation error instead of silently picking +# one (measured 2026-08-28/29: tasking sends landed in a stale default- +# socket twin of the target seat; rc 0 reported honest delivery to the +# wrong pane). Local targets only; remote targets pass -L through as-is. +if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ]; then + socket_dir="${TMPDIR:-/tmp}/tmux-$(id -u)" + hits="" + for sf in "$socket_dir"/*; do + [ -S "$sf" ] || continue + sname="${sf##*/}" + tmux -L "$sname" has-session -t "$DST_SESSION" 2>/dev/null && hits="$hits $sname" + done + hit_count=$(printf '%s' "$hits" | wc -w) + if [ "$hit_count" -gt 1 ]; then + echo "agent-send.sh: REFUSING - session '$DST_SESSION' exists on multiple sockets:$hits" >&2 + echo " Pass -L explicitly (or set MOSAIC_TMUX_SOCKET)." >&2 + exit 4 + elif [ "$hit_count" -eq 1 ]; then + SOCKET_NAME="${hits# }" + elif [ -n "${MOSAIC_TMUX_SOCKET:-}" ]; then + SOCKET_NAME="$MOSAIC_TMUX_SOCKET" + fi +fi +if [ -z "$SOCKET_NAME" ] && [ -n "${MOSAIC_TMUX_SOCKET:-}" ] && [ -n "$SSH_TARGET" ]; then + SOCKET_NAME="$MOSAIC_TMUX_SOCKET" +fi + socket_args=() if [ -n "$SOCKET_NAME" ]; then socket_args=(-L "$SOCKET_NAME") 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..5e72b555 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,8 @@ 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 +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"' EXIT fail() { echo "FAIL: $*" >&2 @@ -79,4 +80,32 @@ 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 +"$AGENT_SEND" -s "$TWIN" -m "must refuse" >/tmp/amb.out 2>/tmp/amb.err || amb_rc=$? +[ "$amb_rc" -eq 4 ] || fail "ambiguity refusal: rc=$amb_rc want 4 (stderr: $(cat /tmp/amb.err))" +grep -q "multiple sockets" /tmp/amb.err || fail "ambiguity refusal message missing socket list" +grep -qF "$SOCKET" /tmp/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 B: session unique to ONE socket, no -L -> auto-resolve to that socket +# and deliver there. +uniq_rc=0 +"$AGENT_SEND" -s "$TARGET" -m "autoresolved hello" >/tmp/uniq.out 2>/tmp/uniq.err || uniq_rc=$? +[ "$uniq_rc" -eq 0 ] || fail "unique auto-resolution: rc=$uniq_rc (stderr: $(cat /tmp/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"