framework tools/tmux: agent-send socket default resolution + ambiguity guard (B1)
ci/woodpecker/pr/ci Pipeline was canceled

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).
This commit is contained in:
2026-08-28 20:41:36 -05:00
parent 34e56f242e
commit 2adfaf1812
2 changed files with 61 additions and 1 deletions
@@ -154,6 +154,37 @@ FULL="${PREAMBLE} ${MSG}"
B64=$(printf '%s' "$FULL" | base64 -w0) B64=$(printf '%s' "$FULL" | base64 -w0)
vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v" 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 <socket> 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=() socket_args=()
if [ -n "$SOCKET_NAME" ]; then if [ -n "$SOCKET_NAME" ]; then
socket_args=(-L "$SOCKET_NAME") socket_args=(-L "$SOCKET_NAME")
@@ -8,7 +8,8 @@ SOCKET="mosaic-test-$RANDOM-$$"
TARGET="target-$RANDOM" TARGET="target-$RANDOM"
DEFAULT_TARGET="default-target-$RANDOM" DEFAULT_TARGET="default-target-$RANDOM"
TMPDIR=$(mktemp -d) 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() { fail() {
echo "FAIL: $*" >&2 echo "FAIL: $*" >&2
@@ -79,4 +80,32 @@ for i in $(seq 1 "$CONC_N"); do
done done
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" echo "ok - named tmux socket send tools"