- 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
20 KiB
Prompt-Lock Fix Implementation Plan (Planner Claude — Final)
- Planner: Planner Claude (
canary-projects-multi-agent-mux-planner-claude) - Date: 2026-07-11
- Brief:
.mam/reports/brief-planner-prompt-lock-plan.md - Inputs consolidated:
- Reviewer Cline —
.agents/reports/canary-projects-multi-agent-mux-reviewer-cline/report-prompt-lock-analysis.md - Creator Claude —
.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-prompt-lock-analysis.md
- Reviewer Cline —
- Executor: Antigravity
- Roadmap linkage: closes FW-W2 (
FUTURE_WORKS.md:25/FUTURE_WORKS.ko.md:24)
0. Consolidation Verdict
Both analyses agree on the root causes (keys sent on timers, not evidence; no dialog detection; wait_for_tui_ready misclassifies dialogs as ready) and on the remedy shape (evidence-based send_keys_safe in lib.sh). I adopt Creator Claude's helper design as the base with four planner amendments (A1–A4 below).
Discrepancy resolved — the delegate-job duplicate. Cline's Site B is real and Creator Claude missed it: .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:365-369 is a raw copy-paste of the inject_instructions body (verified in code; Creator's "sole caller is create_session.sh:384" is technically true of the function but ignores the duplicated logic). This is the highest-traffic delegation path and must be in the mod-sites list (MS-7). I verified lib.sh is side-effect-free at source time (only variable defaults + function definitions), so the delegate-job wrapper can safely source it — this also retires the copy-paste that violates lib.sh's single-source-of-truth mandate (header §4.1).
Planner amendments to Creator's helper:
- A1 — marker derivation: Creator's
head -c 200 | tail -c 24can straddle a newline in the multi-line instructions built atcreate_session.sh:374-381, producing a marker that can never match a single captured line. Amended: last 24 chars of the last non-empty line. - A2 — submit verification: Creator's "marker left
tail -n 5" alone risks a false failure (Claude's TUI echoes the submitted prompt into the transcript just above the input box, so the marker can linger in the bottom 5 lines after a successful submit → spurious retries → exit 4 → spurious rollback). Amended: submitted = marker left the bottom 3 lines AND the pane visibly changed relative to a pre-Enter snapshot. Line count is DoD-tunable (DoD-6). - A3 — dialog detection scope: grep the bottom 20 lines of the pane, not the full viewport — dialog signatures appearing in agent conversation output higher up must not block delivery forever.
- A4 — resume needs an accept-policy helper, not
send_keys_safe:send_keys_safedeliberately refuses to type into dialogs; the resume flow must accept the trust/bypass dialogs. That is a separate policy helper,handle_startup_dialogs(conditional, signature-gated — replaces the blindEnter/Down/Enterboth reports condemned).
Non-goal (out of scope, per surgical principle): adding dialog auto-accept to the create path — it has never had dialog handling; with MS-2/MS-5 a dialog during create now fails loudly with rollback instead of silently prompt-locking. If field data shows trust dialogs during create, a follow-up can reuse handle_startup_dialogs.
1. Deliverable 1 — Concrete Helper Implementation
Insert into .agents/skills/lib.sh immediately after inject_instructions (after current line 1100). Plain bash, no new dependencies.
# ---------------------------------------------------------------------------
# Prompt-lock safe delivery (FW-W2). Keys are sent on evidence, not timers.
# send_keys_safe returns 0 only if the text was verifiably submitted.
# Exit codes: 1=pane never quiesced 2=dialog blocking input
# 3=paste not visible 4=Enter not accepted
# Callers MUST handle non-zero.
# ---------------------------------------------------------------------------
# Server-aware tmux (same isolation rule as inject_instructions).
_sks_tmux() {
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
tmux -L "$TMUX_SERVER_NAME" "$@"
else
tmux "$@"
fi
}
_pane_capture() { _sks_tmux capture-pane -p -t "$1" 2>/dev/null || echo ""; }
# _pane_quiescent <sess> [tries=20] [interval=0.5]
# Renderer settled = two consecutive identical non-empty captures.
# Defeats RC-A (Blessed/Ink renderer bottleneck) without a magic fixed sleep.
_pane_quiescent() {
local sess="$1" tries="${2:-20}" interval="${3:-0.5}" prev="__none__" cur i
for ((i = 0; i < tries; i++)); do
cur=$(_pane_capture "$sess")
[ -n "$cur" ] && [ "$cur" = "$prev" ] && return 0
prev="$cur"
sleep "$interval"
done
return 1
}
# _pane_dialog_open <sess> — focus-stealing modal signatures (trust /
# permission / OAuth / list-selection), checked in the bottom 20 pane lines
# only (dialogs render near the input area; conversation text above must not
# trigger this). Tokens must NOT appear on normal idle prompt screens —
# validate against real captures per agent TUI release (DoD-5).
_pane_dialog_open() {
_pane_capture "$1" | tail -n 20 | grep -Eq \
'Do you trust the files|Yes, proceed|No, exit|Allow this|Press Enter to continue|browser to authenticate|Use arrow keys|Esc to cancel'
}
# send_keys_safe <sess> <text> [job_id]
# 1. Wait for renderer quiescence (RC-A).
# 2. Refuse to paste while a dialog is open (RC-B/RC-C): wait up to
# SKS_DIALOG_TIMEOUT (default 30 s); if SKS_DIALOG_ESCAPE=1, send a single
# Escape per poll and re-check. NEVER a blind Enter — accepting an unknown
# dialog is a policy decision, not a delivery detail.
# 3. Paste via unique buffer; verify the text landed (marker visible).
# 4. Submit C-m; verify submission (marker left the input area AND the pane
# changed); retry up to 3 times — re-Enter on unsubmitted text is idempotent.
send_keys_safe() {
local sess="$1" text="$2" job_id="${3:-adhoc}"
local marker pre_submit deadline try
# Verification token: last 24 chars of the last non-empty line (multi-line safe).
marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | tail -c 24)
_pane_quiescent "$sess" || { echo "send_keys_safe: pane never quiesced ($sess)" >&2; return 1; }
deadline=$(( $(date +%s) + ${SKS_DIALOG_TIMEOUT:-30} ))
while _pane_dialog_open "$sess"; do
if [ "${SKS_DIALOG_ESCAPE:-0}" = "1" ]; then
_sks_tmux send-keys -t "$sess" Escape
sleep 1
fi
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "send_keys_safe: dialog blocking input ($sess)" >&2
return 2
fi
sleep 2
done
_sks_tmux set-buffer -b "sks_$job_id" "$text"
_sks_tmux paste-buffer -b "sks_$job_id" -t "$sess"
_sks_tmux delete-buffer -b "sks_$job_id" 2>/dev/null || true
sleep 0.5
_pane_capture "$sess" | grep -Fq "$marker" || { echo "send_keys_safe: paste not visible ($sess)" >&2; return 3; }
for try in 1 2 3; do
pre_submit=$(_pane_capture "$sess")
_sks_tmux send-keys -t "$sess" C-m
sleep "$try"
# Submitted = input area released the text AND rendering changed after Enter.
if ! _pane_capture "$sess" | tail -n 3 | grep -Fq "$marker" \
&& [ "$(_pane_capture "$sess")" != "$pre_submit" ]; then
return 0
fi
done
echo "send_keys_safe: Enter not accepted after 3 tries ($sess)" >&2
return 4
}
# handle_startup_dialogs <sess> [timeout_sec=20]
# Post-start/resume dialog policy for claude: accept the trust / bypass
# dialogs ONLY when their signature is positively on screen; return as soon
# as the TUI banner is ready. Replaces the blind Enter/Down/Enter sequence.
# Non-fatal by design: on timeout the caller proceeds (attach shows leftovers).
handle_startup_dialogs() {
local sess="$1" timeout="${2:-20}" waited=0 pane
while [ "$waited" -lt "$timeout" ]; do
pane=$(_pane_capture "$sess" | tail -n 20)
if printf '%s\n' "$pane" | grep -q 'Do you trust the files'; then
_sks_tmux send-keys -t "$sess" Enter # accept trust prompt
elif printf '%s\n' "$pane" | grep -q 'Yes, proceed'; then
_sks_tmux send-keys -t "$sess" Down # select "Yes, proceed"
sleep 0.3
_sks_tmux send-keys -t "$sess" Enter
elif printf '%s\n' "$pane" | grep -Eq 'Anthropic|Assistant|Chat|Welcome|projects'; then
return 0 # ready, no dialog
fi
sleep 2
waited=$((waited + 2))
done
return 0
}
⚠️ Signature-token caveat (both analyses inherited this): neither input report captured the actual dialog text of current claude/agy/hermes/cline TUI builds. The token lists above are the best available hypotheses. DoD-5 makes validating them against real
capture-paneoutput a merge blocker — the executor must adjust tokens to observed text before committing.
2. Deliverable 2 — Surgical Mod-Sites List
Every change traces to a verified defect site. No other lines are touched.
| # | File : lines (current) | Change |
|---|---|---|
| MS-1 | .agents/skills/lib.sh (insert after 1100) |
Add the §1 helper block verbatim. |
| MS-2 | .agents/skills/lib.sh:1056 |
wait_for_tui_ready claude regex: "Anthropic|Assistant|Chat|Dangerously|dangerously|Enter|Welcome|projects" → "Anthropic|Assistant|Chat|Welcome|projects" (drop the three tokens that also match trust/bypass dialogs — RC-B fix). |
| MS-3 | .agents/skills/lib.sh:1050-1053 |
Inside the retry loop, before the case: skip the ready-check while a dialog is up — insert if _pane_dialog_open "$sess"; then sleep 1; continue; fi after the capture (requires MS-1 helpers; they are defined later in the file but resolved at call time — bash allows this). |
| MS-4 | .agents/skills/lib.sh:1083 |
echo "⚠️ Warning: ... Proceeding anyway..." → echo "⚠️ TUI readiness check timed out for '$sess'." >&2; return 1 — the gate must be allowed to fail. |
| MS-5 | .agents/skills/lib.sh:1088-1100 |
Rewrite inject_instructions body as a thin wrapper: inject_instructions() { send_keys_safe "$1" "$2" "${3:-onboard}"; } (same signature; sole caller inherits the fix; keep the function comment, note the delegation). |
| MS-6 | .agents/skills/multi-agent-mux-create/scripts/create_session.sh:199 |
wait_for_tui_ready "$SESSION_NAME" "$AGENT" → explicit guard: if ! wait_for_tui_ready "$SESSION_NAME" "$AGENT"; then echo "ERROR: agent TUI never became ready — aborting (rollback via trap)" >&2; exit 1; fi (the trap cleanup_tmux_on_error EXIT armed at line 196 kills the session and removes the isolation home). |
| MS-7 | .agents/skills/multi-agent-mux-create/scripts/create_session.sh:384 |
Guard the injection: if ! inject_instructions "$SESSION_NAME" "$instructions" "$DELEGATE_JOB_ID"; then delegate_publish_event "$DELEGATE_JOB_ID" error "instruction injection failed (prompt-lock, rc=$?)"; exit 1; fi — the job gets a terminal error event (no more zombie jobs waiting for watchdog timeout), then the EXIT trap rolls the session back. started (line 386) now only publishes on verified delivery. |
| MS-8 | .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:364-369 |
Replace the duplicated raw block (set-buffer/paste-buffer/sleep 0.5/C-m/delete-buffer) with: source "$SCRIPT_DIR/../lib.sh" (immediately before use; lib.sh is load-side-effect-free — verified) then if ! send_keys_safe "$sess" "$instructions" "$job_id"; then echo "ERROR: 프롬프트 주입 실패 — 세션 '$sess' (프롬프트 잠금 의심)" >&2; return 1; fi. Keep the local _tmux definition (lines 347-350) — still used by has-session (352) and the attach hint (371). The return 1 propagates under set -euo pipefail and fires the EXIT trap at 358-362, which publishes the error event. |
| MS-9 | .agents/skills/multi-agent-mux-resume/SKILL.md:150-156 |
Replace the blind block (sleep 5; Enter; sleep 3; Down; sleep 0.3; Enter) with: handle_startup_dialogs "$SESSION_NAME" 20 (the embedded script already sources lib.sh at line 68). Fixes both failure directions: no-dialog → no stray keys into the prompt/history; late dialog → polled, not raced. |
| MS-10 | .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh:192 |
tmux send-keys -t "$SESSION_NAME" "$exitkey" Enter 2>/dev/null || true → send_keys_safe "$SESSION_NAME" "$exitkey" "stop$$" || echo "graceful: safe delivery failed (rc=$?) — falling back to kill chain" (script already sources lib.sh at line 32; the SIGTERM→SIGKILL fallback chain at 193-207 stays byte-identical). |
| MS-11 | .agents/skills/multi-agent-mux-create/SKILL.md:214-215 |
Replace the stray-Enter probe example with a passive one: tmux capture-pane -t "$SESSION_NAME" -p -S -20 # TUI ready = agent banner visible, no dialog text (never fire keys as a liveness probe). |
| MS-12 | FUTURE_WORKS.md:25, FUTURE_WORKS.ko.md:24 |
Mark FW-W2 resolved using the existing FW-D1 strikethrough convention (~~...~~ + resolution date 2026-07-11 + commit ref), both languages. |
Explicitly NOT touched (verified non-vulnerable, matching Creator §2-F): update_yaml_resumed.sh, scripts/install_mam.sh, reconcile.sh, all Python backplane scripts.
3. Deliverable 3 — Execution Instructions (for Antigravity)
Run from the repo root, in order. Halt and report back on any non-zero step that isn't explicitly tolerated.
T0 — Preflight
cd /home/godopu16/PuKi/laa/canary_projects/multi-agent-mux
git diff --quiet && git diff --cached --quiet || { echo "ABORT: dirty tree"; exit 1; }
(Untracked files are expected: the two analysis reports and this plan.)
T1 — Apply MS-1…MS-5 to lib.sh
Insert the §1 helper block after line 1100; apply the four edits to wait_for_tui_ready / inject_instructions exactly as specified in §2. Then:
bash -n .agents/skills/lib.sh
T2 — Apply MS-6/MS-7 to create_session.sh, MS-8 to the delegate-job wrapper, MS-9 to resume/SKILL.md, MS-10 to stop_session.sh, MS-11 to create/SKILL.md
bash -n .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job
command -v shellcheck >/dev/null && shellcheck -S warning \
.agents/skills/lib.sh \
.agents/skills/multi-agent-mux-create/scripts/create_session.sh \
.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh || true
Zero new findings allowed (pre-existing findings are out of scope).
T3 — Verification gate (Definition of Done) — scratch server only, never real sessions
All tmux activity on tmux -L sks-test; tmux -L sks-test kill-server afterwards.
- DoD-1 RC-A (renderer stall): mock TUI that floods stdout for 10 s before reading stdin (
while :; do echo spam; done & sleep 10; kill %1; cat) →send_keys_safemust wait out quiescence and deliver (rc 0); confirm by capturing the mock's received line. - DoD-2 RC-B/C (dialog block): mock that prints
Do you trust the files in this folder?and swallows input →send_keys_safemust refuse to paste and return 2 afterSKS_DIALOG_TIMEOUT=6; withSKS_DIALOG_ESCAPE=1verify a single Escape per poll, still no blind Enter. - DoD-3 E2E create: real
create_session.sh --submit-jobon a scratch workspace → instructions verifiably submitted,startedevent observed, normal stop afterwards. - DoD-4 E2E delegate + resume + stop: delegate-job
submitto a running scratch session (delivery via the new path); resume a stopped claude scratch session twice — once where the trust dialog appears, once where it doesn't — confirm no stray keys land in the prompt in the second case;--gracefulstop delivers/exitvia the helper and the fallback chain still engages when the pane is blocked. - DoD-5 signature validation (merge blocker):
capture-panethe real trust/bypass/permission dialogs of the current claude build; confirm every_pane_dialog_openandhandle_startup_dialogstoken matches observed text and none appears on the idle prompt screen; adjust tokens to reality before committing. - DoD-6 A2 tuning: during DoD-3, confirm the submit check (marker leaves bottom-3-lines + pane change) doesn't false-fail on the TUI's transcript echo; tune the
tail -n 3count if needed and record the final value in the commit message.
T4 — Commit (single atomic commit)
git add .agents/skills/lib.sh \
.agents/skills/multi-agent-mux-create/scripts/create_session.sh \
.agents/skills/multi-agent-mux-create/SKILL.md \
.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job \
.agents/skills/multi-agent-mux-resume/SKILL.md \
.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
FUTURE_WORKS.md FUTURE_WORKS.ko.md \
.agents/reports/canary-projects-multi-agent-mux-reviewer-cline/report-prompt-lock-analysis.md \
.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-prompt-lock-analysis.md \
.agents/reports/canary-projects-multi-agent-mux-planner-claude/report-prompt-lock-plan.md
git commit -m "fix(skills): evidence-based prompt delivery (send_keys_safe) to end prompt-lock (FW-W2)
- 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"
T5 — Post-commit sanity
git status --short # expected: empty
grep -n "Proceeding anyway" .agents/skills/lib.sh # expected: no match
grep -rn "sleep 0.5$" .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job # expected: no match
Halt conditions: any DoD failure, any bash -n error, any new shellcheck warning, or dialog tokens that cannot be validated (DoD-5) → stop, do not commit, report back to Planner with the failing capture.
4. Risk Register
| Risk | Severity | Mitigation |
|---|---|---|
| Dialog signature tokens don't match real TUI text | High (silently defeats RC-B fix) | DoD-5 is a merge blocker; tokens curated per agent release |
| A2 submit-check false-fails on transcript echo | Medium (spurious rollback) | Pane-change AND-condition + DoD-6 tuning |
wait_for_tui_ready now failing hard changes create-path behavior on slow hosts |
Medium | 15×1s budget unchanged; failure now rolls back loudly instead of injecting blind — strictly better; monitor first real runs |
Longer stop latency (--graceful quiescence wait) |
Low | Fallback chain untouched; worst case ≈ +10 s before kill-session |
| delegate-job sourcing lib.sh in copied-out installs | Low | Installers ship .agents/skills/ as a tree incl. lib.sh; verified side-effect-free load |
Final Authorization
Plan status: APPROVED for execution by Antigravity exactly as written in §3. DoD-5 (real-capture validation of dialog signatures) is a hard merge blocker. Any deviation halts execution and returns to the Planner.