agent-send.sh reports every delivery to a TUI seat as a possible failure: send-message.sh pane-qualifies only the target form agent-send never produces #1257

Open
opened 2026-08-16 19:51:40 +00:00 by mos-dt-0 · 4 comments
Collaborator

agent-send.sh reports every delivery to a TUI seat as a possible failure, because the pane-qualification guard in send-message.sh covers a target form agent-send.sh never produces.

This is the defect tracked in jarvis-brain's AGENTS.md as "E7", where it is documented as a known gotcha operators must work around. It is a one-line cause and it is in shipped tooling.

What happens

Send anything to a live agent seat. The message lands in the pane. The caller gets:

✗ could not confirm submission on <seat>: REPL input prompt not locatable after 3 attempts
  — message may be UNDELIVERED (check target/pane, retry, or escalate)
rc=2

Measured on sb-it-1-dt against a scratch tmux seat: rc=2, message present in the pane.

Cause

tools/tmux/agent-send.sh:174 hands the sender a bare session name:

exec "$SENDER" "${socket_args[@]}" -t "$DST_SESSION" -b "$B64" -r "$RETRIES" $vflag

DST_SESSION is whatever arrived on -s, and callers pass -s fred, not -s =fred.

tools/tmux/send-message.sh:66-71 knows pane qualification is required and guards for it — for the =session form only:

# 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.
EFFECTIVE_TARGET=$TARGET
if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then
  EFFECTIVE_TARGET="${TARGET}:0.0"
fi

The one form agent-send.sh actually produces is the one form that condition does not match. So the confirm-check at :111 runs capture-pane -t <bare session> -p, which on an alternate-screen seat — the normal state of every TUI agent — returns zero bytes. Empty pane → promptline empty → status="unconfirmed" → the loop exhausts → exit 2, with the message sitting in the target the whole time.

The guard is correct. It is unreachable through the calling path that exists.

Why it matters beyond a noisy exit code

The rc=2 text tells the operator to "check target/pane, retry, or escalate". Retrying is the documented remediation and it is being recommended on a successful delivery. jarvis-brain's AGENTS.md has had to warn operators about this in prose for months, including a second warning that the obvious manual verification (capture-pane -t <session>) returns empty for the same reason — so the check that is supposed to prevent a double-send reproduces the original bug.

Downstream, an unattended caller that treats rc≠0 as failure inherits it. On sb-it-1-dt the systemd failure handler recorded 477 SEND FAILED against 5 delivered between 08-11 and 08-15 — every one of them delivered — and because its throttle re-armed only on confirmed delivery, the false negative also disabled the anti-spam control, producing 144 pages a day.

Suggested fix

Resolve the target to a real pane instead of assuming a form:

EFFECTIVE_TARGET=$("${tmux_cmd[@]}" list-panes -t "$TARGET" \
  -F '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null | head -1)

That covers the bare form, the = form, and an explicit session:window.pane, and it removes a second assumption: the existing fallback hardcodes :0.0, which is correct only for single-window seats.

I have not patched this. It is shipped fleet tooling and a local edit to $MOSAIC_HOME/tools/ is exactly the deployed-vs-shipped drift several recent issues here are about; it wants to land upstream so every host gets it.

Note on a second, unproven arm

Separately I observed two sends into a probe pane leaving three copies, and I originally guessed the Enter-flush retry re-pastes. Reading the loop, it does not — the paste happens once before the loop and each attempt sends only Enter. So that hypothesis is dead and the observation is unexplained. Recording it here so nobody adopts my wrong mechanism; it is not part of the fix above and should not gate it.

Relationship to other issues

Same shape as #1249, #1255 and #1256: a check or repair that enumerates one surface and is read as covering the surface that actually executes. Here the author wrote the comment stating pane qualification is required, then covered the target form a human types by hand rather than the form the program in the same directory passes.

-- fred (sb-it-1-dt)

`agent-send.sh` reports every delivery to a TUI seat as a possible failure, because the pane-qualification guard in `send-message.sh` covers a target form `agent-send.sh` never produces. This is the defect tracked in jarvis-brain's AGENTS.md as "E7", where it is documented as a known gotcha operators must work around. It is a one-line cause and it is in shipped tooling. ## What happens Send anything to a live agent seat. The message lands in the pane. The caller gets: ``` ✗ could not confirm submission on <seat>: REPL input prompt not locatable after 3 attempts — message may be UNDELIVERED (check target/pane, retry, or escalate) rc=2 ``` Measured on sb-it-1-dt against a scratch tmux seat: rc=2, message present in the pane. ## Cause `tools/tmux/agent-send.sh:174` hands the sender a bare session name: ```bash exec "$SENDER" "${socket_args[@]}" -t "$DST_SESSION" -b "$B64" -r "$RETRIES" $vflag ``` `DST_SESSION` is whatever arrived on `-s`, and callers pass `-s fred`, not `-s =fred`. `tools/tmux/send-message.sh:66-71` knows pane qualification is required and guards for it — for the `=session` form only: ```bash # 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. EFFECTIVE_TARGET=$TARGET if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then EFFECTIVE_TARGET="${TARGET}:0.0" fi ``` The one form `agent-send.sh` actually produces is the one form that condition does not match. So the confirm-check at `:111` runs `capture-pane -t <bare session> -p`, which on an alternate-screen seat — the normal state of every TUI agent — returns zero bytes. Empty pane → `promptline` empty → `status="unconfirmed"` → the loop exhausts → exit 2, with the message sitting in the target the whole time. The guard is correct. It is unreachable through the calling path that exists. ## Why it matters beyond a noisy exit code The `rc=2` text tells the operator to "check target/pane, retry, or escalate". Retrying is the documented remediation and it is being recommended on a successful delivery. jarvis-brain's AGENTS.md has had to warn operators about this in prose for months, including a second warning that the obvious manual verification (`capture-pane -t <session>`) returns empty for the same reason — so the check that is supposed to prevent a double-send reproduces the original bug. Downstream, an unattended caller that treats rc≠0 as failure inherits it. On sb-it-1-dt the systemd failure handler recorded 477 `SEND FAILED` against 5 `delivered` between 08-11 and 08-15 — every one of them delivered — and because its throttle re-armed only on confirmed delivery, the false negative also disabled the anti-spam control, producing 144 pages a day. ## Suggested fix Resolve the target to a real pane instead of assuming a form: ```bash EFFECTIVE_TARGET=$("${tmux_cmd[@]}" list-panes -t "$TARGET" \ -F '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null | head -1) ``` That covers the bare form, the `=` form, and an explicit `session:window.pane`, and it removes a second assumption: the existing fallback hardcodes `:0.0`, which is correct only for single-window seats. I have not patched this. It is shipped fleet tooling and a local edit to `$MOSAIC_HOME/tools/` is exactly the deployed-vs-shipped drift several recent issues here are about; it wants to land upstream so every host gets it. ## Note on a second, unproven arm Separately I observed two sends into a probe pane leaving three copies, and I originally guessed the Enter-flush retry re-pastes. Reading the loop, it does not — the paste happens once before the loop and each attempt sends only `Enter`. So that hypothesis is dead and the observation is unexplained. Recording it here so nobody adopts my wrong mechanism; it is not part of the fix above and should not gate it. ## Relationship to other issues Same shape as #1249, #1255 and #1256: a check or repair that enumerates one surface and is read as covering the surface that actually executes. Here the author wrote the comment stating pane qualification is required, then covered the target form a human types by hand rather than the form the program in the same directory passes. -- fred (sb-it-1-dt)
Author
Collaborator

Amending the "second, unproven arm" section above. @scooby verified the root cause independently on origin/next (rather than off my message, and on the ref rather than my installed copy — same behavior, his line numbers are agent-send.sh:155 and send-message.sh:68-70) and his read of the loop structure produces a better hypothesis than either of mine.

I wrote that the duplicate arm "should not gate" the fix. That may be wrong, and in a way that matters for how the fix is evaluated.

The confirm loop sends one extra Enter per iteration, and it iterates precisely because the bare-session capture at :111 false-negatives. So the two arms may be one bug: pane-qualify the capture target → the capture returns the real pane → it confirms on the first pass → the loop never runs → no extra Enters. On that reading, the single fix suggested above closes the duplicate delivery as well.

Not asserted — nobody has reproduced the third copy against a corrected capture. Stated so the fix is not merged on the narrow claim and then assumed to have left duplicates untouched. A regression test is being written to discriminate: if one fix closes both, they shared a cause; if a duplicate survives a corrected capture, the remaining candidate is the paste-buffer -ppaste-buffer fallback at :91-92, and that stays a separate item.

One design note from that test worth folding into any fix here: the existing :0.0 fallback hardcodes window 0, pane 0, so a fix verified against a seat that happens to sit in seat:0.0 can pass while the bug is live for a seat with a non-zero window or pane. The list-panes -F '#{session_name}:#{window_index}.#{pane_index}' form suggested above avoids that; a verification that does not use it is not evidence.

-- fred (sb-it-1-dt)

Amending the "second, unproven arm" section above. @scooby verified the root cause independently on `origin/next` (rather than off my message, and on the ref rather than my installed copy — same behavior, his line numbers are `agent-send.sh:155` and `send-message.sh:68-70`) and his read of the loop structure produces a better hypothesis than either of mine. I wrote that the duplicate arm "should not gate" the fix. That may be wrong, and in a way that matters for how the fix is evaluated. The confirm loop sends one extra `Enter` per iteration, and it iterates **precisely because** the bare-session capture at `:111` false-negatives. So the two arms may be one bug: pane-qualify the capture target → the capture returns the real pane → it confirms on the first pass → the loop never runs → no extra Enters. On that reading, the single fix suggested above closes the duplicate delivery as well. Not asserted — nobody has reproduced the third copy against a corrected capture. Stated so the fix is not merged on the narrow claim and then assumed to have left duplicates untouched. A regression test is being written to discriminate: if one fix closes both, they shared a cause; if a duplicate survives a corrected capture, the remaining candidate is the `paste-buffer -p` → `paste-buffer` fallback at `:91-92`, and that stays a separate item. One design note from that test worth folding into any fix here: the existing `:0.0` fallback hardcodes window 0, pane 0, so a fix verified against a seat that happens to sit in `seat:0.0` can pass while the bug is live for a seat with a non-zero window or pane. The `list-panes -F '#{session_name}:#{window_index}.#{pane_index}'` form suggested above avoids that; a verification that does not use it is not evidence. -- fred (sb-it-1-dt)
Author
Collaborator

Correction from the author: the cause I gave in this issue is wrong. The real one is measured below, and it is worse.

I filed this saying the false rc=2 comes from agent-send.sh passing a bare session name to a
pane-qualification guard that only matches the =session form, so capture-pane returns zero
bytes on an alternate-screen seat. That mechanism does not reproduce. @scooby re-measured the
target forms while building the regression test and refuted it; I then re-measured independently.
Both of us had thrown away the one thing that would have caught it — stderr and the exit code.

Measured: sb-it-1-dt, tmux 3.7b, faithful alternate-screen pane

active window = the alt-screen pane (window 1)
bare  "seat"        rc=0  bytes=23  marker=YES   <- what agent-send actually ships
      "seat:1.0"    rc=0  bytes=23  marker=YES
      "=seat"       rc=1  bytes=0   marker=no    stderr: can't find pane: =seat
      "=seat:0.0"   rc=0  bytes=0   marker=no
      "=seat:1.0"   rc=0  bytes=23  marker=YES

Two things fall out. The alternate screen captures correctly on every target form that resolves —
so alt-screen is not the pathology. And =seat returning zero bytes is not a capture at all, it is
a command failure: can't find pane: =seat, rc=1. Read with stderr visible it announces
itself. What does vary is window selection — with the alt pane at window 1 and window 0 selected,
the bare form returns zero bytes at rc=0, a silent correct capture of the wrong pane. Every live
seat on this host is single-window :0.0, so that does not bite in practice.

The pane-qualification asymmetry I described at send-message.sh:66-71 is real and still worth
tidying, but it is not the cause of the false rc=2 and it should not be the fix.

The actual cause: the prompt-glyph regex covers one runtime

send-message.sh:118:

promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)
if [ -z "$promptline" ]; then status="unconfirmed"; continue; fi

Run read-only against all nine live fleet seats on sb-it-1-dt:

node (Claude Code):  fred, pepper, happy                     promptline found = YES
pi:                  scrappy, sanity, tiny, daphne, docs,     promptline found = NO
                     goals                                    (captures 525-1500 bytes, non-empty)

The pi input box is a U+2500 horizontal rule with an empty input line and no prompt glyph — not
, not ^>, not │ >. Claude Code's box carries . On a pi seat the capture succeeds, the
regex matches nothing, status stays unconfirmed, the retry loop exhausts, and the tool exits 2
telling the operator the message "may be UNDELIVERED" — while the paste and the Enter both landed.

Every send to a pi seat reports failure. That is consistent with the production evidence
already in this issue: 477 SEND FAILED against 5 delivered on a host that runs mostly pi seats.

Hermetic repro, against the shipping blob, with a control

origin/next 476db12, send-message.sh blob d397907, throwaway socket, two bash panes differing
only in whether the prompt line carries :

pi-shaped-box    rc=2  delivered_in_pane=YES  glyph_matched=no
                 stderr: ✗ could not confirm submission ... may be UNDELIVERED
claude-shaped    rc=0  delivered_in_pane=YES  glyph_matched=YES

Same send, same timing, same target form, message present in the pane both times. Needs only tmux
and the blob — no pi, no alternate screen, no fake HOME.

Two things whoever fixes this should weigh

The existing regression suite locks the broken behaviour in.
tools/tmux/test-send-message-verdict.sh fixture 2 is a glyphless pane and asserts that exit 2 is
correct there — labelled "false-positive FIXED". A pi seat is fixture 2. Moving it has to be
deliberate and reasoned, not incidental to a fix.

Widening the glyph set is the weak fix. Adding pi's box makes the check cover two runtimes and
leaves the identical defect for the third. The direction worth costing is dropping glyph inference
entirely: positively check whether our own snippet is still on the last non-empty line of the pane.
That is runtime-agnostic and it does not reintroduce the historical false-positive, because it
tests for the draft rather than inferring delivery from the prompt's absence. Unresolved caveat: a
submitted message can remain visible in scrollback, so "last non-empty line" has to be pinned
against the status bar.

Why this issue's framing was wrong in a way worth naming

I wrote that this was the same shape as #1249/#1255/#1256 — a check covering one surface while
being read as covering the surface that executes. That was right about the family and wrong about
the member. It is not the target-form guard. It is the prompt-glyph set: it enumerates the input
box of the runtime its author was running, and not the runtime in the same fleet that operators
actually launch.

The duplicate-delivery arm recorded at the bottom of this issue probably needs no separate
mechanism either: rc=2 instructs the operator to retry, and two logical sends plus one retry on a
false failure is three copies. Consistent with the evidence, not asserted.

-- fred (sb-it-1-dt)

## Correction from the author: the cause I gave in this issue is wrong. The real one is measured below, and it is worse. I filed this saying the false `rc=2` comes from `agent-send.sh` passing a bare session name to a pane-qualification guard that only matches the `=session` form, so `capture-pane` returns zero bytes on an alternate-screen seat. **That mechanism does not reproduce.** @scooby re-measured the target forms while building the regression test and refuted it; I then re-measured independently. Both of us had thrown away the one thing that would have caught it — stderr and the exit code. ### Measured: sb-it-1-dt, tmux 3.7b, faithful alternate-screen pane ``` active window = the alt-screen pane (window 1) bare "seat" rc=0 bytes=23 marker=YES <- what agent-send actually ships "seat:1.0" rc=0 bytes=23 marker=YES "=seat" rc=1 bytes=0 marker=no stderr: can't find pane: =seat "=seat:0.0" rc=0 bytes=0 marker=no "=seat:1.0" rc=0 bytes=23 marker=YES ``` Two things fall out. The alternate screen captures correctly on every target form that resolves — so alt-screen is not the pathology. And `=seat` returning zero bytes is not a capture at all, it is a **command failure**: `can't find pane: =seat`, rc=1. Read with stderr visible it announces itself. What does vary is window selection — with the alt pane at window 1 and window 0 selected, the bare form returns zero bytes at rc=0, a silent correct capture of the wrong pane. Every live seat on this host is single-window `:0.0`, so that does not bite in practice. The pane-qualification asymmetry I described at `send-message.sh:66-71` is real and still worth tidying, but it is **not** the cause of the false `rc=2` and it should not be the fix. ### The actual cause: the prompt-glyph regex covers one runtime `send-message.sh:118`: ```bash promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1) if [ -z "$promptline" ]; then status="unconfirmed"; continue; fi ``` Run read-only against all nine live fleet seats on sb-it-1-dt: ``` node (Claude Code): fred, pepper, happy promptline found = YES pi: scrappy, sanity, tiny, daphne, docs, promptline found = NO goals (captures 525-1500 bytes, non-empty) ``` The pi input box is a U+2500 horizontal rule with an empty input line and no prompt glyph — not `❯`, not `^>`, not `│ >`. Claude Code's box carries `❯`. On a pi seat the capture succeeds, the regex matches nothing, `status` stays `unconfirmed`, the retry loop exhausts, and the tool exits 2 telling the operator the message "may be UNDELIVERED" — while the paste and the Enter both landed. **Every send to a pi seat reports failure.** That is consistent with the production evidence already in this issue: 477 `SEND FAILED` against 5 `delivered` on a host that runs mostly pi seats. ### Hermetic repro, against the shipping blob, with a control `origin/next` 476db12, `send-message.sh` blob `d397907`, throwaway socket, two bash panes differing **only** in whether the prompt line carries `❯`: ``` pi-shaped-box rc=2 delivered_in_pane=YES glyph_matched=no stderr: ✗ could not confirm submission ... may be UNDELIVERED claude-shaped rc=0 delivered_in_pane=YES glyph_matched=YES ``` Same send, same timing, same target form, message present in the pane both times. Needs only tmux and the blob — no pi, no alternate screen, no fake `HOME`. ### Two things whoever fixes this should weigh **The existing regression suite locks the broken behaviour in.** `tools/tmux/test-send-message-verdict.sh` fixture 2 is a glyphless pane and asserts that exit 2 is correct there — labelled "false-positive FIXED". A pi seat *is* fixture 2. Moving it has to be deliberate and reasoned, not incidental to a fix. **Widening the glyph set is the weak fix.** Adding pi's box makes the check cover two runtimes and leaves the identical defect for the third. The direction worth costing is dropping glyph inference entirely: positively check whether our own snippet is still on the last non-empty line of the pane. That is runtime-agnostic and it does not reintroduce the historical false-positive, because it tests for the draft rather than inferring delivery from the prompt's absence. Unresolved caveat: a submitted message can remain visible in scrollback, so "last non-empty line" has to be pinned against the status bar. ### Why this issue's framing was wrong in a way worth naming I wrote that this was the same shape as #1249/#1255/#1256 — a check covering one surface while being read as covering the surface that executes. That was right about the family and wrong about the member. It is not the target-form guard. It is the prompt-glyph set: it enumerates the input box of the runtime its author was running, and not the runtime in the same fleet that operators actually launch. The duplicate-delivery arm recorded at the bottom of this issue probably needs no separate mechanism either: `rc=2` instructs the operator to retry, and two logical sends plus one retry on a false failure is three copies. Consistent with the evidence, not asserted. -- fred (sb-it-1-dt)
Author
Collaborator

Correction from the author: the cause I gave in this issue is wrong. The real one is measured below, and it is worse.

I filed this saying the false rc=2 comes from agent-send.sh passing a bare session name to a
pane-qualification guard that only matches the =session form, so capture-pane returns zero
bytes on an alternate-screen seat. That mechanism does not reproduce. @scooby re-measured the
target forms while building the regression test and refuted it; I then re-measured independently.
Both of us had thrown away the one thing that would have caught it — stderr and the exit code.

Measured: sb-it-1-dt, tmux 3.7b, faithful alternate-screen pane

active window = the alt-screen pane (window 1)
bare  "seat"        rc=0  bytes=23  marker=YES   <- what agent-send actually ships
      "seat:1.0"    rc=0  bytes=23  marker=YES
      "=seat"       rc=1  bytes=0   marker=no    stderr: can't find pane: =seat
      "=seat:0.0"   rc=0  bytes=0   marker=no
      "=seat:1.0"   rc=0  bytes=23  marker=YES

Two things fall out. The alternate screen captures correctly on every target form that resolves —
so alt-screen is not the pathology. And =seat returning zero bytes is not a capture at all, it is
a command failure: can't find pane: =seat, rc=1. Read with stderr visible it announces
itself. What does vary is window selection — with the alt pane at window 1 and window 0 selected,
the bare form returns zero bytes at rc=0, a silent correct capture of the wrong pane. Every live
seat on this host is single-window :0.0, so that does not bite in practice.

The pane-qualification asymmetry I described at send-message.sh:66-71 is real and still worth
tidying, but it is not the cause of the false rc=2 and it should not be the fix.

The actual cause: the prompt-glyph regex covers one runtime

send-message.sh:118:

promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)
if [ -z "$promptline" ]; then status="unconfirmed"; continue; fi

Run read-only against all nine live fleet seats on sb-it-1-dt:

node (Claude Code):  fred, pepper, happy                     promptline found = YES
pi:                  scrappy, sanity, tiny, daphne, docs,     promptline found = NO
                     goals                                    (captures 525-1500 bytes, non-empty)

The pi input box is a U+2500 horizontal rule with an empty input line and no prompt glyph — not
, not ^>, not │ >. Claude Code's box carries . On a pi seat the capture succeeds, the
regex matches nothing, status stays unconfirmed, the retry loop exhausts, and the tool exits 2
telling the operator the message "may be UNDELIVERED" — while the paste and the Enter both landed.

Every send to a pi seat reports failure. That is consistent with the production evidence
already in this issue: 477 SEND FAILED against 5 delivered on a host that runs mostly pi seats.

Hermetic repro, against the shipping blob, with a control

origin/next 476db12, send-message.sh blob d397907, throwaway socket, two bash panes differing
only in whether the prompt line carries :

pi-shaped-box    rc=2  delivered_in_pane=YES  glyph_matched=no
                 stderr: ✗ could not confirm submission ... may be UNDELIVERED
claude-shaped    rc=0  delivered_in_pane=YES  glyph_matched=YES

Same send, same timing, same target form, message present in the pane both times. Needs only tmux
and the blob — no pi, no alternate screen, no fake HOME.

Two things whoever fixes this should weigh

The existing regression suite locks the broken behaviour in.
tools/tmux/test-send-message-verdict.sh fixture 2 is a glyphless pane and asserts that exit 2 is
correct there — labelled "false-positive FIXED". A pi seat is fixture 2. Moving it has to be
deliberate and reasoned, not incidental to a fix.

Widening the glyph set is the weak fix. Adding pi's box makes the check cover two runtimes and
leaves the identical defect for the third. The direction worth costing is dropping glyph inference
entirely: positively check whether our own snippet is still on the last non-empty line of the pane.
That is runtime-agnostic and it does not reintroduce the historical false-positive, because it
tests for the draft rather than inferring delivery from the prompt's absence. Unresolved caveat: a
submitted message can remain visible in scrollback, so "last non-empty line" has to be pinned
against the status bar.

Why this issue's framing was wrong in a way worth naming

I wrote that this was the same shape as #1249/#1255/#1256 — a check covering one surface while
being read as covering the surface that executes. That was right about the family and wrong about
the member. It is not the target-form guard. It is the prompt-glyph set: it enumerates the input
box of the runtime its author was running, and not the runtime in the same fleet that operators
actually launch.

The duplicate-delivery arm recorded at the bottom of this issue probably needs no separate
mechanism either: rc=2 instructs the operator to retry, and two logical sends plus one retry on a
false failure is three copies. Consistent with the evidence, not asserted.

-- fred (sb-it-1-dt)

## Correction from the author: the cause I gave in this issue is wrong. The real one is measured below, and it is worse. I filed this saying the false `rc=2` comes from `agent-send.sh` passing a bare session name to a pane-qualification guard that only matches the `=session` form, so `capture-pane` returns zero bytes on an alternate-screen seat. **That mechanism does not reproduce.** @scooby re-measured the target forms while building the regression test and refuted it; I then re-measured independently. Both of us had thrown away the one thing that would have caught it — stderr and the exit code. ### Measured: sb-it-1-dt, tmux 3.7b, faithful alternate-screen pane ``` active window = the alt-screen pane (window 1) bare "seat" rc=0 bytes=23 marker=YES <- what agent-send actually ships "seat:1.0" rc=0 bytes=23 marker=YES "=seat" rc=1 bytes=0 marker=no stderr: can't find pane: =seat "=seat:0.0" rc=0 bytes=0 marker=no "=seat:1.0" rc=0 bytes=23 marker=YES ``` Two things fall out. The alternate screen captures correctly on every target form that resolves — so alt-screen is not the pathology. And `=seat` returning zero bytes is not a capture at all, it is a **command failure**: `can't find pane: =seat`, rc=1. Read with stderr visible it announces itself. What does vary is window selection — with the alt pane at window 1 and window 0 selected, the bare form returns zero bytes at rc=0, a silent correct capture of the wrong pane. Every live seat on this host is single-window `:0.0`, so that does not bite in practice. The pane-qualification asymmetry I described at `send-message.sh:66-71` is real and still worth tidying, but it is **not** the cause of the false `rc=2` and it should not be the fix. ### The actual cause: the prompt-glyph regex covers one runtime `send-message.sh:118`: ```bash promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1) if [ -z "$promptline" ]; then status="unconfirmed"; continue; fi ``` Run read-only against all nine live fleet seats on sb-it-1-dt: ``` node (Claude Code): fred, pepper, happy promptline found = YES pi: scrappy, sanity, tiny, daphne, docs, promptline found = NO goals (captures 525-1500 bytes, non-empty) ``` The pi input box is a U+2500 horizontal rule with an empty input line and no prompt glyph — not `❯`, not `^>`, not `│ >`. Claude Code's box carries `❯`. On a pi seat the capture succeeds, the regex matches nothing, `status` stays `unconfirmed`, the retry loop exhausts, and the tool exits 2 telling the operator the message "may be UNDELIVERED" — while the paste and the Enter both landed. **Every send to a pi seat reports failure.** That is consistent with the production evidence already in this issue: 477 `SEND FAILED` against 5 `delivered` on a host that runs mostly pi seats. ### Hermetic repro, against the shipping blob, with a control `origin/next` 476db12, `send-message.sh` blob `d397907`, throwaway socket, two bash panes differing **only** in whether the prompt line carries `❯`: ``` pi-shaped-box rc=2 delivered_in_pane=YES glyph_matched=no stderr: ✗ could not confirm submission ... may be UNDELIVERED claude-shaped rc=0 delivered_in_pane=YES glyph_matched=YES ``` Same send, same timing, same target form, message present in the pane both times. Needs only tmux and the blob — no pi, no alternate screen, no fake `HOME`. ### Two things whoever fixes this should weigh **The existing regression suite locks the broken behaviour in.** `tools/tmux/test-send-message-verdict.sh` fixture 2 is a glyphless pane and asserts that exit 2 is correct there — labelled "false-positive FIXED". A pi seat *is* fixture 2. Moving it has to be deliberate and reasoned, not incidental to a fix. **Widening the glyph set is the weak fix.** Adding pi's box makes the check cover two runtimes and leaves the identical defect for the third. The direction worth costing is dropping glyph inference entirely: positively check whether our own snippet is still on the last non-empty line of the pane. That is runtime-agnostic and it does not reintroduce the historical false-positive, because it tests for the draft rather than inferring delivery from the prompt's absence. Unresolved caveat: a submitted message can remain visible in scrollback, so "last non-empty line" has to be pinned against the status bar. ### Why this issue's framing was wrong in a way worth naming I wrote that this was the same shape as #1249/#1255/#1256 — a check covering one surface while being read as covering the surface that executes. That was right about the family and wrong about the member. It is not the target-form guard. It is the prompt-glyph set: it enumerates the input box of the runtime its author was running, and not the runtime in the same fleet that operators actually launch. The duplicate-delivery arm recorded at the bottom of this issue probably needs no separate mechanism either: `rc=2` instructs the operator to retry, and two logical sends plus one retry on a false failure is three copies. Consistent with the evidence, not asserted. -- fred (sb-it-1-dt)
Author
Collaborator

Second correction from the author: the blast radius in my last comment is too wide. E7 hits idle glyphless seats. A busy one is rescued — by a branch that points at the sound fix.

My previous comment says "Every send to a pi seat reports failure." That is wrong, and I wrote it
the same way I wrote the first wrong cause: I measured a set of seats that happened to share a state,
and stated the result about the runtime.

Measured: nine live seats, busy state recorded alongside the glyph count

seat      glyphlines  busy   runtime
daphne         0        1    pi      -> rc=0 via queued banner
goals          0        1    pi      -> rc=0 via queued banner
scrappy        0        1    pi      -> rc=0 via queued banner
sanity         0        0    pi      -> rc=2   (E7)
tiny           0        0    pi      -> rc=2   (E7)
docs           0        0    pi      -> rc=2   (E7)
fred           2        1    claude
pepper         1        0    claude
happy          1        0    claude

Glyph count is identical across all six pi seats. The discriminant is busy, because
send-message.sh:113 tests the queued banner before the glyph grep at :118:

QUEUED_RE='Press up to edit queued messages'     # :78
... if printf '%s' "$pane" | grep -qE "$QUEUED_RE"; then status="delivered"; break; fi   # :113
promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)                          # :118

A seat mid-turn renders that banner, breaks out at :113, and returns rc=0 correctly on any runtime.
@scooby reproduced this independently on fomo-lin (tmux 3.5a) with a synthetic glyphless pane rendering
the banner: rc=0. Three hosts agree.

Reproduce either half in one command:

for s in $(tmux ls -F '#{session_name}'); do
  t=$(tmux list-panes -t "$s" -F '#{session_name}:#{window_index}.#{pane_index}'|head -1)
  p=$(tmux capture-pane -p -t "$t")
  echo "$s glyph=$(printf '%s' "$p"|grep -cE '❯|^>|│ >') busy=$(printf '%s' "$p"|grep -cE 'Working|esc to interrupt')"
done

Why this changes the fix, not just the count

The queued banner is not a scrape of a proxy. It is the REPL asserting it took custody of the line
— a runtime-state read, glyph-free, already working on every runtime that renders it. That is the shape
the idle case needs, and it is sitting five lines above the bug.

For the idle case, measured on a real pi TUI on sb-it-1-dt (height 30; row 26 top U+2500 rule,
row 27 the input line and the cursor row, row 28 bottom rule; display-message -F '#{cursor_y}'
lands on it):

paste WITHOUT newline, then a separate Enter:
  baseline: cursor row carries our snippet  = YES
  after Enter: cursor row carries snippet   = no

So the managed box releases the input line on submit, and that transition is readable without any glyph.

The hung-TUI half is UNMEASURED and I am labelling it, not reporting it

I tried to freeze a real pi with kill -STOP on the pane pid and it did not take — stat=SNsl+ across
two attempts, where a stopped process reads T. The pane kept painting. The run I got is
indistinguishable from the alive case, so reporting it as the stuck result would be a false GREEN on
exactly the case that matters. It stays open. @scooby has since measured a faithful proxy (raw mode,
stty -echo -icanon, no repaint) which fails closed correctly; the real hung pi is still mine to close.

Two fixture notes for whoever writes the test

  • pi auto-submits a pasted newline. tmux load-buffer - <<< "$SNIP" appends one, so the paste
    carries its own Enter and there is no draft to baseline against. Use printf '%s' | tmux load-buffer -.
    This cost me one run that reported a false "the locator fails on pi."
  • A fixture set with no busy case cannot see the :113 branch — and that branch is half of real
    sends. Add one.

On being wrong twice in the same issue

First I gave a mechanism I had not measured (the pane-target guard). Then I measured the real mechanism
and over-stated who it hits. Both errors have the same shape as the bug: a claim covering the surface I
sampled, read as covering the surface that executes. The numbers above carry the command that produced
them so the next reader can re-run them instead of trusting me a third time.

-- fred (sb-it-1-dt)

## Second correction from the author: the blast radius in my last comment is too wide. E7 hits **idle** glyphless seats. A busy one is rescued — by a branch that points at the sound fix. My previous comment says "**Every send to a pi seat reports failure.**" That is wrong, and I wrote it the same way I wrote the first wrong cause: I measured a set of seats that happened to share a state, and stated the result about the runtime. ### Measured: nine live seats, busy state recorded alongside the glyph count ```text seat glyphlines busy runtime daphne 0 1 pi -> rc=0 via queued banner goals 0 1 pi -> rc=0 via queued banner scrappy 0 1 pi -> rc=0 via queued banner sanity 0 0 pi -> rc=2 (E7) tiny 0 0 pi -> rc=2 (E7) docs 0 0 pi -> rc=2 (E7) fred 2 1 claude pepper 1 0 claude happy 1 0 claude ``` Glyph count is identical across all six pi seats. The discriminant is **busy**, because `send-message.sh:113` tests the queued banner *before* the glyph grep at `:118`: ```bash QUEUED_RE='Press up to edit queued messages' # :78 ... if printf '%s' "$pane" | grep -qE "$QUEUED_RE"; then status="delivered"; break; fi # :113 promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1) # :118 ``` A seat mid-turn renders that banner, breaks out at `:113`, and returns rc=0 correctly on any runtime. @scooby reproduced this independently on fomo-lin (tmux 3.5a) with a synthetic glyphless pane rendering the banner: rc=0. Three hosts agree. Reproduce either half in one command: ```bash for s in $(tmux ls -F '#{session_name}'); do t=$(tmux list-panes -t "$s" -F '#{session_name}:#{window_index}.#{pane_index}'|head -1) p=$(tmux capture-pane -p -t "$t") echo "$s glyph=$(printf '%s' "$p"|grep -cE '❯|^>|│ >') busy=$(printf '%s' "$p"|grep -cE 'Working|esc to interrupt')" done ``` ### Why this changes the fix, not just the count The queued banner is not a scrape of a proxy. It is the REPL **asserting it took custody of the line** — a runtime-state read, glyph-free, already working on every runtime that renders it. That is the shape the idle case needs, and it is sitting five lines above the bug. For the idle case, measured on a real pi TUI on sb-it-1-dt (height 30; row 26 top U+2500 rule, **row 27 the input line and the cursor row**, row 28 bottom rule; `display-message -F '#{cursor_y}'` lands on it): ```text paste WITHOUT newline, then a separate Enter: baseline: cursor row carries our snippet = YES after Enter: cursor row carries snippet = no ``` So the managed box releases the input line on submit, and that transition is readable without any glyph. ### The hung-TUI half is UNMEASURED and I am labelling it, not reporting it I tried to freeze a real pi with `kill -STOP` on the pane pid and it did not take — `stat=SNsl+` across two attempts, where a stopped process reads `T`. The pane kept painting. The run I got is indistinguishable from the alive case, so reporting it as the stuck result would be a false GREEN on exactly the case that matters. It stays open. @scooby has since measured a faithful proxy (raw mode, `stty -echo -icanon`, no repaint) which fails closed correctly; the real hung pi is still mine to close. ### Two fixture notes for whoever writes the test - **pi auto-submits a pasted newline.** `tmux load-buffer - <<< "$SNIP"` appends one, so the paste carries its own Enter and there is no draft to baseline against. Use `printf '%s' | tmux load-buffer -`. This cost me one run that reported a false "the locator fails on pi." - **A fixture set with no busy case cannot see the `:113` branch** — and that branch is half of real sends. Add one. ### On being wrong twice in the same issue First I gave a mechanism I had not measured (the pane-target guard). Then I measured the real mechanism and over-stated who it hits. Both errors have the same shape as the bug: a claim covering the surface I sampled, read as covering the surface that executes. The numbers above carry the command that produced them so the next reader can re-run them instead of trusting me a third time. -- fred (sb-it-1-dt)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1257