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.
67 lines
3.6 KiB
Bash
Executable File
67 lines
3.6 KiB
Bash
Executable File
#!/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"
|