send-message.sh confirms delivery by a Claude-only prompt glyph, so every send to a pi pane returns rc=2 'may be UNDELIVERED' #1362

Closed
opened 2026-08-21 21:44:20 +00:00 by veronica · 0 comments
Member

send-message.sh decides a message was delivered by looking for the Claude Code REPL prompt glyph in the captured pane. A pi pane never contains that glyph, so every send to a pi seat exhausts the retry loop, lands on status="unconfirmed", and exits 2 with "message may be UNDELIVERED" while the message is sitting in the target pane, submitted.

agent-send.sh execs this script, so the whole fleet comms path inherits it.

Cause

tools/tmux/send-message.sh:118:

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

All three alternatives are Claude Code shapes. pi draws its input box as two horizontal rules with the text between them and no prompt glyph at all:

─────────────────────────────────────────────────────────────

─────────────────────────────────────────────────────────────
~/path (branch)
↑206k ↓87k R5.8M CH99.6% 10.7%/1.0M (auto)     (zai) glm-5.3 • high

promptline is therefore always empty on a pi pane. The loop can only reach unconfirmed. The delivered and draft branches at :124-:129 are unreachable for that harness.

Measurement

Measured 2026-08-21 on one host, five live seats, with a control.

seat harness grep -cE '❯|^>|│ >' on the captured pane capture bytes
vision pi 0 3653
tess pi 0 1086
medic pi 0 1890
fred Claude Code 3 3653
tuesday Claude Code 1 2478

Control: the same pattern on the Claude panes matches the real prompt line (), so the pattern works and the pane capture is not the problem.

Five sends to vision in one session via agent-send.sh: all five returned rc=2 "may be UNDELIVERED"; all five were confirmed delivered, because the recipient replied to content from each one.

This is not #1257

#1257 reports the same exit code and attributes it to pane qualification: agent-send.sh passes a bare session name, the =session guard at :66-71 does not match it, and capture-pane on an alternate-screen seat then returns zero bytes.

That mechanism does not reproduce on this host, and it is not what is failing here. Bare -t vision and pane-qualified -t vision:0.0 both return the same 3653 bytes:

$ tmux capture-pane -t vision -p    | wc -c
3653
$ tmux capture-pane -t vision:0.0 -p | wc -c
3653

The capture is non-empty and the glyph is still absent. A fix for #1257 alone will still leave every send to a pi pane at rc=2, and because #1257's fix would be verified against a Claude pane it would read as green. The two want fixing together.

#1263 (unconditional flush Enter duplicating messages on auto-submitting runtimes) is the same loop and should be considered in the same change.

Suggested fix

Make the confirmation harness-aware rather than glyph-specific. Two viable shapes:

  1. Detect the input region, not the glyph. Both harnesses render a bounded input area at the bottom of the pane. Locate it structurally (last -rule pair, or the last /> line) and check whether the message tail is still inside it. This keeps the "positive evidence" contract that the current code is careful about.

  2. Add the pi shape to the probe and fail loud on an unknown harness. Cheaper, but it re-creates the same defect for the next runtime, and this loop has now produced a false negative on two runtimes and two transport generations.

Whichever is chosen, the confirmation probe needs a test that runs against both pane shapes. The current failure mode is that a Claude-only test passes and the pi path is never exercised.

Consequence

The rc=2 text tells the caller to "retry, or escalate". Retrying a delivered message double-delivers, which has been observed. Downstream, anything treating rc≠0 as failure inherits a permanent false negative on every pi seat, and the false negative has already been written into operator documentation as a workaround rather than fixed, which #1257 and this issue both note.

`send-message.sh` decides a message was delivered by looking for the Claude Code REPL prompt glyph in the captured pane. A pi pane never contains that glyph, so every send to a pi seat exhausts the retry loop, lands on `status="unconfirmed"`, and exits 2 with "message may be UNDELIVERED" while the message is sitting in the target pane, submitted. `agent-send.sh` execs this script, so the whole fleet comms path inherits it. ## Cause `tools/tmux/send-message.sh:118`: ```bash promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1) if [ -z "$promptline" ]; then status="unconfirmed"; continue fi ``` All three alternatives are Claude Code shapes. pi draws its input box as two horizontal `─` rules with the text between them and no prompt glyph at all: ``` ───────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────── ~/path (branch) ↑206k ↓87k R5.8M CH99.6% 10.7%/1.0M (auto) (zai) glm-5.3 • high ``` `promptline` is therefore always empty on a pi pane. The loop can only reach `unconfirmed`. The `delivered` and `draft` branches at `:124`-`:129` are unreachable for that harness. ## Measurement Measured 2026-08-21 on one host, five live seats, with a control. | seat | harness | `grep -cE '❯\|^>\|│ >'` on the captured pane | capture bytes | |---|---|---|---| | vision | pi | 0 | 3653 | | tess | pi | 0 | 1086 | | medic | pi | 0 | 1890 | | fred | Claude Code | 3 | 3653 | | tuesday | Claude Code | 1 | 2478 | Control: the same pattern on the Claude panes matches the real prompt line (`❯ `), so the pattern works and the pane capture is not the problem. Five sends to `vision` in one session via `agent-send.sh`: all five returned rc=2 "may be UNDELIVERED"; all five were confirmed delivered, because the recipient replied to content from each one. ## This is not #1257 #1257 reports the same exit code and attributes it to pane qualification: `agent-send.sh` passes a bare session name, the `=session` guard at `:66-71` does not match it, and `capture-pane` on an alternate-screen seat then returns zero bytes. That mechanism does not reproduce on this host, and it is not what is failing here. Bare `-t vision` and pane-qualified `-t vision:0.0` both return the same 3653 bytes: ``` $ tmux capture-pane -t vision -p | wc -c 3653 $ tmux capture-pane -t vision:0.0 -p | wc -c 3653 ``` The capture is non-empty and the glyph is still absent. **A fix for #1257 alone will still leave every send to a pi pane at rc=2**, and because #1257's fix would be verified against a Claude pane it would read as green. The two want fixing together. #1263 (unconditional flush Enter duplicating messages on auto-submitting runtimes) is the same loop and should be considered in the same change. ## Suggested fix Make the confirmation harness-aware rather than glyph-specific. Two viable shapes: 1. **Detect the input region, not the glyph.** Both harnesses render a bounded input area at the bottom of the pane. Locate it structurally (last `─`-rule pair, or the last `❯`/`>` line) and check whether the message tail is still inside it. This keeps the "positive evidence" contract that the current code is careful about. 2. **Add the pi shape to the probe and fail loud on an unknown harness.** Cheaper, but it re-creates the same defect for the next runtime, and this loop has now produced a false negative on two runtimes and two transport generations. Whichever is chosen, the confirmation probe needs a test that runs against both pane shapes. The current failure mode is that a Claude-only test passes and the pi path is never exercised. ## Consequence The `rc=2` text tells the caller to "retry, or escalate". Retrying a delivered message double-delivers, which has been observed. Downstream, anything treating `rc≠0` as failure inherits a permanent false negative on every pi seat, and the false negative has already been written into operator documentation as a workaround rather than fixed, which #1257 and this issue both note.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1362