Stale [y/n/t]: permission prompt in scrollback causes get_status() to return WAITING_USER_ANSWER even when terminal is idle
After a user answers a permission prompt (y/n/t), the [y/n/t]: text remains in tmux scrollback. On subsequent get_status() calls, permission_prompt_pattern matches the stale prompt text because \s* in the regex bridges across \n characters, connecting the old ]: with the current idle prompt.
Root Cause
In providers/kiro_cli.py (and q_cli.py):
self._permission_prompt_pattern = (
r"Allow this action\?.*\[.*y.*\/.*n.*\/.*t.*\]:\s*" + self._idle_prompt_pattern
)
The pattern requires [y/n/t]: followed by \s* then the idle prompt. \s includes [\r\n\t\f\v ], so \s* bridges across newlines connecting stale ]: text to the current idle prompt.
Why This Bug Exists — Introduced by PR #61
This bug was introduced by PR #61 (484fd46, Feb 5, 2026).
Before PR #61: The idle prompt pattern ended with [\s\n]*$ (dollar anchor). The $ forced the idle prompt to be at the end of the captured text. Even with re.DOTALL and \s*, stale permission prompts could not match because the idle prompt immediately after [y/n/t]: was NOT at end-of-string — there was more output after it.
After PR #61: The $ anchor was removed. Now permission_prompt_pattern matches any [y/n/t]: followed (via \s* bridging newlines) by any idle prompt anywhere in scrollback — including stale ones from minutes ago.
# Scrollback contains:
Allow this action? ... [y/n/t]: ← stale (answered minutes ago)
\n
[agent-name] 11% > ← current idle prompt
\s* matches \n, bridging the stale ]: to the current idle prompt. The re.DOTALL flag lets .* span the permission prompt text across wrapped lines, but \s* is the actual culprit — even without re.DOTALL, the match succeeds via \s*.
Summary: PR #61 removed the $ anchor that was protecting against stale matches. The fix restores the protection by a different mechanism: blocking newline bridging instead of requiring end-of-string.
Impact
get_status() permanently returns WAITING_USER_ANSWER after any permission prompt interaction
- The inbox watcher skips delivery (terminal not IDLE/COMPLETED)
- All subsequent
send_message deliveries to that terminal are stuck as PENDING
- The terminal is effectively broken for the rest of the session
Reproduction Steps
- Start a CAO session with kiro-cli supervisor
- Ask the agent to write a file (triggers permission prompt)
- Answer
y at the [y/n/t]: prompt
- Wait for the agent to complete and return to idle
- Check status:
curl -s http://localhost:9889/terminals/{terminal_id} | python3 -m json.tool
- Observe:
"status": "waiting_user_answer" — should be completed or idle
Evidence from Repro (mainline commit 5302bc7)
Terminal 4b73ce4c:
Actual state: idle at prompt "[cao-task-scoping-supervisor] 11% >"
API status: "waiting_user_answer" ← WRONG
Inbox message 387: status=pending (stuck, never delivered)
Tmux pane shows the idle prompt clearly, but stale [y/n/t]: is in scrollback above.
Suggested Fix
Replace \s* with [ \t]* in the permission prompt pattern:
# Before (buggy — \s* bridges across newlines):
r"Allow this action\?.*\[.*y.*\/.*n.*\/.*t.*\]:\s*" + self._idle_prompt_pattern
# After (fixed — [ \t]* only matches horizontal whitespace):
r"Allow this action\?.*\[.*y.*\/.*n.*\/.*t.*\]:[ \t]*" + self._idle_prompt_pattern
re.DOTALL should be kept on the re.search() call — it is needed for narrow terminals where the permission prompt text wraps across lines. The fix is specifically \s* → [ \t]* to block newline bridging.
Environment
- CAO version: mainline (
5302bc7)
- kiro-cli version: 1.25.0
- OS: macOS
Stale
[y/n/t]:permission prompt in scrollback causesget_status()to returnWAITING_USER_ANSWEReven when terminal is idleAfter a user answers a permission prompt (
y/n/t), the[y/n/t]:text remains in tmux scrollback. On subsequentget_status()calls,permission_prompt_patternmatches the stale prompt text because\s*in the regex bridges across\ncharacters, connecting the old]:with the current idle prompt.Root Cause
In
providers/kiro_cli.py(andq_cli.py):The pattern requires
[y/n/t]:followed by\s*then the idle prompt.\sincludes[\r\n\t\f\v ], so\s*bridges across newlines connecting stale]:text to the current idle prompt.Why This Bug Exists — Introduced by PR #61
This bug was introduced by PR #61 (
484fd46, Feb 5, 2026).Before PR #61: The idle prompt pattern ended with
[\s\n]*$(dollar anchor). The$forced the idle prompt to be at the end of the captured text. Even withre.DOTALLand\s*, stale permission prompts could not match because the idle prompt immediately after[y/n/t]:was NOT at end-of-string — there was more output after it.After PR #61: The
$anchor was removed. Nowpermission_prompt_patternmatches any[y/n/t]:followed (via\s*bridging newlines) by any idle prompt anywhere in scrollback — including stale ones from minutes ago.\s*matches\n, bridging the stale]:to the current idle prompt. There.DOTALLflag lets.*span the permission prompt text across wrapped lines, but\s*is the actual culprit — even withoutre.DOTALL, the match succeeds via\s*.Summary: PR #61 removed the
$anchor that was protecting against stale matches. The fix restores the protection by a different mechanism: blocking newline bridging instead of requiring end-of-string.Impact
get_status()permanently returnsWAITING_USER_ANSWERafter any permission prompt interactionsend_messagedeliveries to that terminal are stuck as PENDINGReproduction Steps
yat the[y/n/t]:promptcurl -s http://localhost:9889/terminals/{terminal_id} | python3 -m json.tool"status": "waiting_user_answer"— should becompletedoridleEvidence from Repro (mainline commit
5302bc7)Tmux pane shows the idle prompt clearly, but stale
[y/n/t]:is in scrollback above.Suggested Fix
Replace
\s*with[ \t]*in the permission prompt pattern:re.DOTALLshould be kept on there.search()call — it is needed for narrow terminals where the permission prompt text wraps across lines. The fix is specifically\s*→[ \t]*to block newline bridging.Environment
5302bc7)