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:
2026-08-24 09:47:57 -05:00
parent 9014a510a9
commit a77afe6778
3 changed files with 94 additions and 6 deletions
@@ -64,13 +64,31 @@ if [ -n "$SOCKET_NAME" ]; then
tmux_cmd+=(-L "$SOCKET_NAME")
fi
# tmux accepts `=session` for some commands, but pane-level commands such as
# capture-pane require a pane-qualified target. Keep exact-session addressing
# convenient while avoiding accidental prefix matches.
# Normalise the target to an EXACT session plus a window part, because tmux
# resolves the two halves with different and individually dangerous defaults:
#
# * 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
if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then
EFFECTIVE_TARGET="${TARGET}:0.0"
fi
case "$TARGET" in
=*|%*|@*|\$*) ;;
*) EFFECTIVE_TARGET="=$TARGET" ;;
esac
case "$EFFECTIVE_TARGET" in
=*) [[ "$EFFECTIVE_TARGET" == *:* ]] || EFFECTIVE_TARGET="${EFFECTIVE_TARGET}:" ;;
esac
# Target must resolve to a live pane.
if ! "${tmux_cmd[@]}" list-panes -t "$EFFECTIVE_TARGET" >/dev/null 2>&1; then