CI 2604 red on scripts/pipefail-early-exit.test.mjs. Two lines I added in the
previous commit tripped its scan of send-message.sh:
[ "$(printf '%s\n' "$rule_lines" | grep -c .)" -eq 2 ] || return 1
top=$(printf '%s\n' "$rule_lines" | head -1)
The second is a real violation of the rule the guard enforces: `head` exits
after its count, the producer takes SIGPIPE, and under `set -euo pipefail` the
caller aborts with rc=141 and no output. That is the same failure mode
test-mosaic-worktree-large-repo.sh exists to pin.
The first is the guard reading `-eq` as an early-exit grep flag
(`-[A-Za-z]*q` matches `-eq`), so `grep ... | ... -eq 2` looks like `grep -q`
on one line. A false positive, but the fix removes the shape either way.
Both go away by splitting the captured line numbers with parameter expansion
instead of a second pass through the pipe. With one rule captured, both halves
resolve to the same value and the new `[ "$top" != "$bottom" ]` test rejects it,
which is the answer that case wanted anyway.
Verified: node --test scripts/pipefail-early-exit.test.mjs green on the
load-bearing assertion; test-send-message-verdict.sh still PASS=5 FAIL=0;
test-send-message-socket.sh rc=0; agent-send.test.sh PASS=19 FAIL=0.
send-message.sh confirmed delivery by grepping the captured pane for a
prompt glyph (`❯`, a leading `>`, or `│ >`). Those are Claude Code shapes.
pi draws its input box as two horizontal `─` rules with the input between
them and no glyph anywhere, so on a pi pane the grep matched nothing, the
`delivered` and `draft` branches were unreachable, and every send exited 2
with "may be UNDELIVERED" after burning all its retries — on messages that
had in fact been delivered.
Measured on five live seats: pi seats vision, tess and medic returned 0
glyph matches on non-empty captures (3653 / 1086 / 1890 bytes); Claude
seats fred and tuesday returned 3 and 1.
This is a different arm from #1257. On the measured host
`tmux capture-pane -t <session> -p` and `-t <session>:0.0 -p` both return
the same 3653 bytes, so #1257's empty-capture mechanism does not reproduce
here; fixing only that would leave every pi send at rc=2 and would verify
green against a Claude pane.
The probe becomes locate_input_box(), which returns a status code instead
of a string. Found-but-empty is a real answer (an empty input box is what
a submitted message leaves behind) and is now distinguishable from
not-found, which the old `[ -z "$promptline" ]` test conflated. Box
detection anchors on the LAST pair of `─` rules: agent output can contain
its own rules, but nothing is drawn below the input box except the status
line.
Adding a runtime means adding its shape in that one function. A missing
shape does not degrade gracefully — it turns every send to that runtime
into a false "may be UNDELIVERED", which is exactly what this fixes.
Tests: two fixtures added to test-send-message-verdict.sh driving a
glyphless box-drawn REPL, one that submits (expect exit 0 delivered) and
one that holds the text in the box (expect exit 2 draft). Patched suite
PASS=5 FAIL=0. Control against the pristine origin/next send-message.sh:
PASS=3 FAIL=2, fixture 4 failing as `rc=2 ... REPL input prompt not
locatable after 3 attempts — message may be UNDELIVERED`, which is the
defect reproduced in a test. Sibling suites unchanged and green:
test-send-message-socket.sh rc=0, agent-send.test.sh PASS=19 FAIL=0.
Note the suite is in test-enumeration-exclusions.txt (the CI image ships
no tmux), so these fixtures are verified locally only. That exclusion and
its #1017 burndown reason are unchanged by this commit.
Not addressed here: auto-submit-drafts.sh lines 20 and 26 carry the same
Claude-only `❯` assumption. Separate surface, separate change.
Closes#1362
2026-08-21 16:54:39 -05:00
2 changed files with 107 additions and 9 deletions
# Input box located AND clear of our tail => positively submitted. This is the
# Input box located AND clear of our tail => positively submitted. This is the
@@ -135,6 +176,6 @@ case "$status" in
delivered)echo"✓ delivered to $TARGET";exit0;;
delivered)echo"✓ delivered to $TARGET";exit0;;
queued)echo"✓ queued to $TARGET (agent busy — will process when it returns to prompt)";exit0;;
queued)echo"✓ queued to $TARGET (agent busy — will process when it returns to prompt)";exit0;;
draft)echo"✗ still an unsubmitted draft on $TARGET after $RETRIES flush attempts" >&2;exit2;;
draft)echo"✗ still an unsubmitted draft on $TARGET after $RETRIES flush attempts" >&2;exit2;;
unconfirmed)echo"✗ could not confirm submission on $TARGET: REPL input prompt not locatable after $((RETRIES +1)) attempts — message may be UNDELIVERED (check target/pane, retry, or escalate)" >&2;exit2;;
unconfirmed)echo"✗ could not confirm submission on $TARGET: REPL input box not locatable after $((RETRIES +1)) attempts — message may be UNDELIVERED (check target/pane, retry, or escalate)" >&2;exit2;;
*)echo"✗ could not confirm submission on $TARGET (unexpected state '$status')" >&2;exit2;;
*)echo"✗ could not confirm submission on $TARGET (unexpected state '$status')" >&2;exit2;;
ok "draft: message left in a glyphless box => exit 2 + 'unsubmitted draft'"
else
no "draft: message left in a glyphless box => exit 2 + stderr""rc=$rc err=[$(cat "$TMP/e5")]"
fi
fi
echo"---"
echo"---"
echo"PASS=$PASS FAIL=$FAIL"
echo"PASS=$PASS FAIL=$FAIL"
["$FAIL" -eq 0]
["$FAIL" -eq 0]
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.