- Add send_keys_safe + quiescence/dialog-detection helpers to lib.sh: keys are sent on pane evidence, never fixed timers; distinct exit codes 1-4; dialogs are never blindly Enter-ed - inject_instructions delegates to send_keys_safe; create --submit-job publishes a terminal error event on delivery failure (no zombie jobs) - wait_for_tui_ready: drop dialog-ambiguous tokens, treat open dialogs as not-ready, return 1 on timeout instead of proceeding - delegate-job wrapper: replace copy-pasted raw paste/C-m block with lib.sh send_keys_safe (restores single source of truth) - resume: conditional signature-gated dialog handling replaces blind Enter/Down/Enter; stop --graceful delivers exitkey safely, fallback chain unchanged - create SKILL: passive capture-pane probe instead of stray Enter - Mark FW-W2 resolved; add 3-agent analysis/plan reports
5.4 KiB
Prompt-Lock & Input Delivery Failure — Code-Level Analysis Report
- Reviewer: Reviewer Cline (
canary-projects-multi-agent-mux-reviewer-cline, role: reviewer) - Date: 2026-07-11
- Brief:
.mam/reports/brief-prompt-lock-fix.md - Scope: Code-level analysis of the "prompt typing lock / input delivery failure" in the TMUX-based multi-agent environment; concrete prevention helper + migration plan.
1. The Problem (recap)
When the orchestrator issues commands via tmux send-keys to a target agent TUI:
- Text gets printed inside the prompt input box but is never submitted (Enter ignored) or the cursor freezes.
- Root causes: blessed UI renderer thread bottleneck during heavy output; dialog popups (Approve/Reject permission prompts) stealing input focus; OAuth/list-selection dialog blocks intercepting keystrokes.
The core failure mode is: send-keys delivers keystrokes to whatever currently has focus. If a permission dialog, an OAuth browser-prompt, or a list-selection popup is open, the keystrokes go to the dialog (or are swallowed), not the main input box — so the prompt text appears but Enter does nothing, or the cursor appears frozen.
2. Exact Code Locations — Every send-keys / Input-Delivery Site
I grepped the entire .agents/ tree. There are 6 input-delivery sites; only 2 use a helper, the rest are raw tmux send-keys.
Site A — lib.sh:1086-1100 (inject_instructions) — HELPER, central
inject_instructions() {
local sess="$1" instructions="$2" job_id="${3:-onboard}"
local local_tmux="tmux"
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
local_tmux="tmux -L $TMUX_SERVER_NAME"
fi
$local_tmux set-buffer -b "job_buf_$job_id" "$instructions"
$local_tmux paste-buffer -b "job_buf_$job_id" -t "$sess"
sleep 0.5
$local_tmux send-keys -t "$sess" C-m # ← Enter, no focus guard
$local_tmux delete-buffer -b "job_buf_$job_id"
}
Vulnerability: No focus recovery. If a permission/dialog popup is open when C-m fires, Enter goes to the dialog. The fixed sleep 0.5 is too short under heavy renderer load (the brief's "renderer thread bottleneck"). No delivery verification.
Site B — multi-agent-mux-delegate-job:364-369 — DUPLICATE of Site A, raw
$_tmux set-buffer -b "job_buf_$job_id" "$instructions"
$_tmux paste-buffer -b "job_buf_$job_id" -t "$sess"
sleep 0.5
$_tmux send-keys -t "$sess" C-m
$_tmux delete-buffer -b "job_buf_$job_id"
Vulnerability: Identical logic to Site A, copy-pasted (violates lib.sh's "single source of truth" mandate, lib.sh header §4.1). Same no-focus-guard + too-short-sleep defects. This is the delegate-job path — the primary way the orchestrator hands work to agents, so it's the highest-traffic vulnerable site.
Site C — resume/SKILL.md:150-156 — RAW, dialog auto-handle (MOST VULNERABLE)
# auto-handle trust / bypass dialogs
sleep 5
tmux send-keys -t "$SESSION_NAME" Enter 2>/dev/null || true
sleep 3
tmux send-keys -t "$SESSION_NAME" Down 2>/dev/null || true
sleep 0.3
tmux send-keys -t "$SESSION_NAME" Enter 2>/dev/null || true
Vulnerability: This is the exact lock symptom from the brief. It fires blind Enter/Down/Enter on fixed sleeps to auto-dismiss a trust dialog. Problems: (a) no check that a dialog actually exists — if the TUI rendered late and focus is still the main input, these keystrokes type garbage into the prompt; (b) if a different dialog (OAuth, list-select) appeared instead of the expected trust prompt, Down+Enter selects the wrong option; (c) 2>/dev/null || true swallows all errors silently — the operator never learns delivery failed; (d) no wait_for_tui_ready gate before sending.
Site D — stop_session.sh:192 (graceful_stop) — RAW
tmux send-keys -t "$SESSION_NAME" "$exitkey" Enter 2>/dev/null || true
Vulnerability: Sends /exit + Enter without focus recovery. If a permission dialog is open, /exit is typed into the dialog (harmless there) but Enter may dismiss the dialog with an unintended choice, and the agent never receives the exit command. The graceful chain does have a proper fallback (kill-session → SIGTERM → SIGKILL with has-session checks, lines 194-205), so this site is low-severity — but it still benefits from focus recovery.
Site E — create/SKILL.md:215 — RAW, probe (documentation example)
tmux send-keys -t "$SESSION_NAME" "" Enter
Vulnerability: This is in a verification snippet (sends empty + Enter). Low impact — it's an optional manual probe, not an automated path. But it sets a bad example for users.
Site F — create_session.sh:384 — USES HELPER (Site A)
inject_instructions "$SESSION_NAME" "$instructions" "$DELEGATE_JOB_ID"
Status: This is the correct pattern — it routes through inject_instructions. It's only as safe as Site A. Also note: create_session.sh:199 calls wait_for_tui_ready before any input — the only site that does so.
Existing mitigation already present (good, underused)
lib.sh:1040-1084 defines wait_for_tui_ready <sess> <agent> — a gated capture-pane loop (15×1s) that grep-checks the pane content for each agent's TUI banner before returning. This is exactly the right primitive, but it is only called in create_session.sh:199. Resume, stop, and delegate-job never gate on TUI readiness.