Summary
send-prompt.sh send returns delivered (exit 0) for a prompt that was pasted into the worker's REPL but never submitted. The worker then sits idle holding an unsubmitted [Pasted text #N] buffer while the coordinator believes the phase was handed off.
The guard for exactly this already exists in launch-session.sh — it is missing from send-prompt.sh, which handles every prompt after the first (§2 implement, §3 rework, §4 merge-prep).
Observed
Orchestrating NEWRTB-2786 (2 tmux workers, dev-loop 1.6.0, macOS, Claude Code).
- Sent the §2 implement prompt:
sh {SKILL}/scripts/send-prompt.sh send lo-1-2786 "<one-line implement prompt>"
→ stdout: delivered
→ exit: 0
-
watch-status.sh --tasks t1 <status-dir> impl_done 1 later exited 7 (worker stalled).
-
tmux capture-pane showed the prompt still sitting in the input buffer, unsubmitted:
❯ [Pasted text #3]one item explicitly with the actual grep output pasted, then run STATUS_DIR=...
...status-update.sh t1 impl_done worktree=$PWD and then WAIT at the REPL. Do not commit, push or open a PR.
send-prompt.sh keys lo-1-2786 Enter submitted it; the worker started work within seconds.
The task had been idle from the delivered until the stall watcher fired.
Root cause
send-prompt.sh (v1.6.0, the send path):
"$TMUX_BIN" send-keys -t "$(target_pane "$1")" -l -- "$2" || failed=1
[ "$failed" -eq 1 ] || "$TMUX_BIN" send-keys -t "$(target_pane "$1")" Enter || failed=1
...
sleep "$confirm_delay"
after=$(pane_tail "$1") || ...
if printf '%s' "$after" | grep -qF -- "$queued_pat" 2>/dev/null; then
note_pane "$1"; echo "queued"; exit 4
fi
if [ "$after" != "$before" ]; then
echo "delivered"; exit 0 # <-- reached in this case
fi
note_pane "$1"; echo "unconfirmed"; exit 7
The confirmation is "did the pane change". When the REPL collapses a long literal paste into a [Pasted text #N] placeholder, the pane does change — so the pasted-but-unsubmitted state satisfies the delivered branch. The trailing Enter is absorbed by the paste rather than submitting it.
The existing comment in that block anticipates the queued false positive ("A busy pane still ECHOES the typed characters") but not the pasted-placeholder one.
The fix already exists in the sibling script
launch-session.sh handles this and even documents the failure in its header:
- header (lines ~19-20): a session sat on
"[Pasted text #1 +1 lines]" for 45 minutes while the script printed ok; a single extra Enter then ran it.
- line ~241:
elif printf '%s' "$tail_pane" | grep -qF '[Pasted text'; then — detects the placeholder and sends additional Enter(s).
- line ~264: reports
ok: ... (confirmed after $sent_extra extra Enter).
In my run launch-session.sh did exactly that for the §1 prompt (confirmed after 1 extra Enter) — so the first prompt was safe and only the later ones were exposed.
Suggested fix
Port the [Pasted text check into send-prompt.sh's send confirmation, ordered before the generic "pane changed" test (same load-bearing ordering the existing queued_pat check relies on):
- If the pane tail contains
[Pasted text, the prompt is unsubmitted — send an extra Enter and re-check (bounded retries, as launch-session.sh does).
- If it still shows the placeholder after the retries, return a distinct non-zero code rather than
delivered, so the coordinator can branch on it.
Alternatively, factor the confirmation out of launch-session.sh into a shared helper both scripts call, so the two paths cannot drift again.
Impact
delivered is the signal the orchestrate skill tells coordinators to branch on (Phase 4: "Branch on the exit code"). A false delivered converts into idle wall-clock time bounded only by LO_PHASE_TIMEOUTS or the stall detector — in my run the stall watcher caught it, but a coordinator that trusts exit 0 and waits on impl_done would burn the full impl_done=3600 budget.
Environment
- dev-loop 1.6.0
- macOS (Darwin 25.5.0), tmux 3.x from Homebrew
- substrate: tmux (chosen at Gate 1)
Summary
send-prompt.sh sendreturnsdelivered(exit 0) for a prompt that was pasted into the worker's REPL but never submitted. The worker then sits idle holding an unsubmitted[Pasted text #N]buffer while the coordinator believes the phase was handed off.The guard for exactly this already exists in
launch-session.sh— it is missing fromsend-prompt.sh, which handles every prompt after the first (§2 implement, §3 rework, §4 merge-prep).Observed
Orchestrating NEWRTB-2786 (2 tmux workers, dev-loop 1.6.0, macOS, Claude Code).
watch-status.sh --tasks t1 <status-dir> impl_done 1later exited 7 (worker stalled).tmux capture-paneshowed the prompt still sitting in the input buffer, unsubmitted:send-prompt.sh keys lo-1-2786 Entersubmitted it; the worker started work within seconds.The task had been idle from the
delivereduntil the stall watcher fired.Root cause
send-prompt.sh(v1.6.0, thesendpath):The confirmation is "did the pane change". When the REPL collapses a long literal paste into a
[Pasted text #N]placeholder, the pane does change — so the pasted-but-unsubmitted state satisfies thedeliveredbranch. The trailingEnteris absorbed by the paste rather than submitting it.The existing comment in that block anticipates the queued false positive ("A busy pane still ECHOES the typed characters") but not the pasted-placeholder one.
The fix already exists in the sibling script
launch-session.shhandles this and even documents the failure in its header:"[Pasted text #1 +1 lines]"for 45 minutes while the script printedok; a single extra Enter then ran it.elif printf '%s' "$tail_pane" | grep -qF '[Pasted text'; then— detects the placeholder and sends additional Enter(s).ok: ... (confirmed after $sent_extra extra Enter).In my run
launch-session.shdid exactly that for the §1 prompt (confirmed after 1 extra Enter) — so the first prompt was safe and only the later ones were exposed.Suggested fix
Port the
[Pasted textcheck intosend-prompt.sh'ssendconfirmation, ordered before the generic "pane changed" test (same load-bearing ordering the existingqueued_patcheck relies on):[Pasted text, the prompt is unsubmitted — send an extraEnterand re-check (bounded retries, aslaunch-session.shdoes).delivered, so the coordinator can branch on it.Alternatively, factor the confirmation out of
launch-session.shinto a shared helper both scripts call, so the two paths cannot drift again.Impact
deliveredis the signal the orchestrate skill tells coordinators to branch on (Phase 4: "Branch on the exit code"). A falsedeliveredconverts into idle wall-clock time bounded only byLO_PHASE_TIMEOUTSor the stall detector — in my run the stall watcher caught it, but a coordinator that trusts exit 0 and waits onimpl_donewould burn the fullimpl_done=3600budget.Environment