fix(tmux): resolve send-message targets to an exact session and window
tmux resolves the two halves of a target with different, individually
dangerous defaults, and send-message.sh took both defaults:
* An unpinned name PREFIX-matches. With `foobar` alive and no `foo`,
`-t foo` resolves to `foobar` at rc=0 -- pasted, Enter-ed, verified
and reported OK against the wrong agent's pane.
* A bare `=name` is only half a pin. capture-pane REJECTS it ("can't
find pane") while list-panes silently PREFIX-MATCHES it, and the
validation at :76 uses list-panes -- so for any caller already
supplying `=name`, that rewrite was the only thing between them and
a wrong-session pass.
The direction is what makes this expensive. Paste (:93-94), Enter (:151)
and the verifying capture (:153) all read one EFFECTIVE_TARGET, so a
wrong-window send is confirmed by a wrong-window read: it manufactures a
false "delivered", not a loud failure. A false negative gets
investigated; a false positive gets believed.
Normalise to `=session:` -- exact session, active window. Explicit tmux
ids (%pane, @window, $session) pass through untouched.
BEHAVIOUR CHANGE for callers that already pass `=name`: they previously
landed on `:0.0` (window 0 unconditionally) and now land on the session's
ACTIVE window. This is the intended fix -- window 0 is not where a
multi-window agent is sitting -- but it does move a live target rather
than being a no-op normalisation.
Test: test-send-message-target.sh covers all four arms (absent name must
not prefix-match, delivery follows the active window, an explicit window
part is preserved, a unique prefix is still refused). Proven able to go
red: against the pre-fix script it FAILs at arm 1, and with arm 1 removed
it FAILs at arm 2. The multi-window fixture is load-bearing -- a
single-window session cannot tell `=s:` from `=s:0.0`, which is why this
survived.
It is registered as a signed enumeration exclusion rather than on a CI
surface: it drives a real tmux server and the CI image ships no tmux,
the same condition its two siblings are already excluded under. It
hard-fails when tmux is absent rather than skipping, so it cannot go
quietly green where it cannot run.
This commit is contained in:
@@ -29,6 +29,10 @@ packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh | unmeasured i
|
|||||||
# --- tools/tmux: require a live tmux server ---
|
# --- tools/tmux: require a live tmux server ---
|
||||||
packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion)
|
packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion)
|
||||||
packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling)
|
packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling)
|
||||||
|
# The entry below is NOT covered by the #1017 signature block above: it was
|
||||||
|
# signed by jarvis-enhance (dragon-lin, 2026-08-24) at a later base, for the
|
||||||
|
# test added alongside the send-message.sh exact-target fix.
|
||||||
|
packages/mosaic/framework/tools/tmux/test-send-message-target.sh | requires a real tmux server on a throwaway socket, and specifically a MULTI-WINDOW session (the bug it guards is invisible on a single-window fixture); CI image ships no tmux; same burndown condition as its two siblings above
|
||||||
|
|
||||||
# --- single-suite directories: unmeasured in CI ---
|
# --- single-suite directories: unmeasured in CI ---
|
||||||
|
|
||||||
|
|||||||
@@ -64,13 +64,31 @@ if [ -n "$SOCKET_NAME" ]; then
|
|||||||
tmux_cmd+=(-L "$SOCKET_NAME")
|
tmux_cmd+=(-L "$SOCKET_NAME")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# tmux accepts `=session` for some commands, but pane-level commands such as
|
# Normalise the target to an EXACT session plus a window part, because tmux
|
||||||
# capture-pane require a pane-qualified target. Keep exact-session addressing
|
# resolves the two halves with different and individually dangerous defaults:
|
||||||
# convenient while avoiding accidental prefix matches.
|
#
|
||||||
|
# * An unpinned name is a PREFIX match. With a session `foobar` alive and
|
||||||
|
# no session `foo`, `-t foo` resolves to `foobar` at rc=0, so a message is
|
||||||
|
# delivered, verified and reported OK against the wrong agent's pane.
|
||||||
|
# * A bare `=name` is not enough on its own: capture-pane REJECTS it
|
||||||
|
# ("can't find pane") while list-panes silently PREFIX-MATCHES it, so the
|
||||||
|
# validation below would pass on a session the capture cannot read.
|
||||||
|
# * A trailing `:` follows the session's ACTIVE window. Pinning `:0.0`
|
||||||
|
# instead addresses window 0 unconditionally, and since the paste, the
|
||||||
|
# Enter and the verifying capture all use EFFECTIVE_TARGET, a multi-window
|
||||||
|
# agent gets typed into window 0 and confirmed by reading window 0 --
|
||||||
|
# a false "delivered" rather than a loud failure.
|
||||||
|
#
|
||||||
|
# Explicit tmux ids (%pane, @window, $session) are passed through untouched;
|
||||||
|
# prefixing `=` to them would break addressing that is already unambiguous.
|
||||||
EFFECTIVE_TARGET=$TARGET
|
EFFECTIVE_TARGET=$TARGET
|
||||||
if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then
|
case "$TARGET" in
|
||||||
EFFECTIVE_TARGET="${TARGET}:0.0"
|
=*|%*|@*|\$*) ;;
|
||||||
fi
|
*) EFFECTIVE_TARGET="=$TARGET" ;;
|
||||||
|
esac
|
||||||
|
case "$EFFECTIVE_TARGET" in
|
||||||
|
=*) [[ "$EFFECTIVE_TARGET" == *:* ]] || EFFECTIVE_TARGET="${EFFECTIVE_TARGET}:" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Target must resolve to a live pane.
|
# Target must resolve to a live pane.
|
||||||
if ! "${tmux_cmd[@]}" list-panes -t "$EFFECTIVE_TARGET" >/dev/null 2>&1; then
|
if ! "${tmux_cmd[@]}" list-panes -t "$EFFECTIVE_TARGET" >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Target normalisation: send-message.sh must address an EXACT session and the
|
||||||
|
# session's ACTIVE window. Both halves have caused silent wrong-pane delivery:
|
||||||
|
# * an unpinned name prefix-matches, so a message for an absent session is
|
||||||
|
# delivered to a different agent and reported OK;
|
||||||
|
# * a `:0.0` pin addresses window 0 regardless of where the agent is, and
|
||||||
|
# because the paste, the Enter and the verifying capture share one target,
|
||||||
|
# the wrong window is also the window that confirms the send.
|
||||||
|
# Both rows below FAIL against the pre-fix script, which is the point of them.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
SEND_MESSAGE="$SCRIPT_DIR/send-message.sh"
|
||||||
|
SOCKET="mosaic-test-target-$RANDOM-$$"
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; rm -rf "$TMPDIR"' EXIT
|
||||||
|
|
||||||
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||||
|
command -v tmux >/dev/null 2>&1 || fail "tmux is required"
|
||||||
|
|
||||||
|
tmux_() { tmux -L "$SOCKET" "$@"; }
|
||||||
|
newsess() { tmux_ new-session -d -s "$1" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i'; }
|
||||||
|
hits() { tmux_ capture-pane -p -t "$1" 2>/dev/null | grep -cF "$2" || true; }
|
||||||
|
|
||||||
|
# ── 1. an absent session must not prefix-match a live one ───────────────────
|
||||||
|
newsess sibling-long
|
||||||
|
nonce="absent-target-$RANDOM"
|
||||||
|
rc=0; "$SEND_MESSAGE" -L "$SOCKET" -t sibling -m "$nonce" >/dev/null 2>&1 || rc=$?
|
||||||
|
[ "$rc" -ne 0 ] || fail "send to absent session 'sibling' returned rc=0 (prefix-matched)"
|
||||||
|
[ "$(hits sibling-long "$nonce")" -eq 0 ] || fail "message for absent 'sibling' was delivered to 'sibling-long'"
|
||||||
|
|
||||||
|
# positive control: the detector above can see a real delivery
|
||||||
|
nonce_ok="control-$RANDOM"
|
||||||
|
"$SEND_MESSAGE" -L "$SOCKET" -t sibling-long -m "$nonce_ok" >/dev/null 2>&1 \
|
||||||
|
|| fail "send to a live session failed"
|
||||||
|
[ "$(hits sibling-long "$nonce_ok")" -gt 0 ] || fail "control: live delivery not observed — detector is blind"
|
||||||
|
|
||||||
|
# ── 2. delivery follows the ACTIVE window, not window 0 ─────────────────────
|
||||||
|
# A single-window fixture cannot tell `=s:` from `=s:0.0`; the active window
|
||||||
|
# must be non-zero or this test proves nothing.
|
||||||
|
newsess multi
|
||||||
|
tmux_ new-window -t multi -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i'
|
||||||
|
tmux_ select-window -t multi:1
|
||||||
|
active=$(tmux_ display-message -p -t multi '#{window_index}')
|
||||||
|
[ "$active" = "1" ] || fail "fixture setup: expected active window 1, got $active"
|
||||||
|
|
||||||
|
for target in multi "=multi"; do
|
||||||
|
nonce="active-win-$RANDOM"
|
||||||
|
"$SEND_MESSAGE" -L "$SOCKET" -t "$target" -m "$nonce" >/dev/null 2>&1 \
|
||||||
|
|| fail "send to '$target' failed"
|
||||||
|
[ "$(hits multi:1 "$nonce")" -gt 0 ] || fail "'$target' did not deliver to the active window"
|
||||||
|
[ "$(hits multi:0 "$nonce")" -eq 0 ] || fail "'$target' delivered to window 0 instead of the active window"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── 3. an explicit window part is preserved ─────────────────────────────────
|
||||||
|
nonce="explicit-win-$RANDOM"
|
||||||
|
"$SEND_MESSAGE" -L "$SOCKET" -t multi:0 -m "$nonce" >/dev/null 2>&1 || fail "send to 'multi:0' failed"
|
||||||
|
[ "$(hits multi:0 "$nonce")" -gt 0 ] || fail "explicit 'multi:0' did not deliver to window 0"
|
||||||
|
|
||||||
|
# ── 4. a unique prefix of a live session is still refused ───────────────────
|
||||||
|
nonce="prefix-$RANDOM"
|
||||||
|
rc=0; "$SEND_MESSAGE" -L "$SOCKET" -t mult -m "$nonce" >/dev/null 2>&1 || rc=$?
|
||||||
|
[ "$rc" -ne 0 ] || fail "send to prefix 'mult' returned rc=0"
|
||||||
|
[ "$(hits multi:1 "$nonce")" -eq 0 ] || fail "prefix 'mult' was delivered to 'multi'"
|
||||||
|
|
||||||
|
echo "PASS: send-message.sh target normalisation"
|
||||||
Reference in New Issue
Block a user