Summary
run_in_terminal() from prompt_toolkit is an async function that always returns a coroutine. Multiple call sites in cli.py invoke it without await and without a fallback, causing two distinct bugs that share the same root cause.
Bug A — Silent output loss in _cprint() (line 1515)
File: cli.py, function _cprint(), inside closure _schedule()
def _schedule():
try:
run_in_terminal(lambda: _pt_print(_PT_ANSI(text))) # NOT awaited
except Exception:
try:
_pt_print(_PT_ANSI(text))
except Exception:
pass
try:
loop.call_soon_threadsafe(_schedule)
except Exception:
...
What happens: run_in_terminal() returns a coroutine that is passed to call_soon_threadsafe. The callback is scheduled, but the coroutine object itself is never awaited — it gets garbage collected by Python, triggering RuntimeWarning: coroutine 'run_in_terminal.<locals>.run' was never awaited.
Impact: When _cprint fires from a background thread (e.g., process_loop, voice auto-restart thread, self-improvement summaries), the printed text is silently dropped. The warning message "process_loop unhandled error (msg may be lost)" at line 12338 is the symptom of this.
Trigger: Background thread calls _cprint while the prompt_toolkit app loop is running on a different thread — specifically when get_running_loop() succeeds and differs from app.loop.
Bug B — Broken input prompt on WSL in _prompt_text_input() (line 5876)
File: cli.py, function _prompt_text_input()
def _prompt_text_input(self, prompt_text: str) -> str | None:
...
if self._app:
from prompt_toolkit.application import run_in_terminal
...
try:
run_in_terminal(_ask) # NOT awaited, no fallback on WSL
finally:
...
What happens: On WSL terminals (Warp, PowerShell, etc.), the run_in_terminal() coroutine is scheduled but the event loop fails silently to execute it. No exception propagates up, no fallback runs, result[0] stays None.
Impact: The input prompt never displays. User keystrokes go to the agent buffer instead of the confirmation prompt — making it appear the agent is consuming their input.
Root Cause
prompt_toolkit.application.run_in_terminal() (v3.0.52+):
def run_in_terminal(func: Callable[[], _T], ...) -> Awaitable[_T]:
async def run() -> _T:
async with in_terminal(...):
return func()
return ensure_future(run()) # always returns a coroutine (Future)
Every call site must either await the returned coroutine or wrap in try/except with a direct fallback.
Fix Summary
- Bug A (
_cprint): Await the coroutine via asyncio.ensure_future() inside the callback, or use loop.call_soon_threadsafe with asyncio.create_task pattern.
- Bug B (
_prompt_text_input): Add except Exception: _ask() after run_in_terminal(_ask) to provide the direct fallback when run_in_terminal fails on WSL.
Environment:
prompt_toolkit>=3.0.52,<4
- Python 3.11
- WSL (for Bug B)
- Background thread scenario (for Bug A)
Summary
run_in_terminal()from prompt_toolkit is anasyncfunction that always returns a coroutine. Multiple call sites incli.pyinvoke it withoutawaitand without a fallback, causing two distinct bugs that share the same root cause.Bug A — Silent output loss in
_cprint()(line 1515)File:
cli.py, function_cprint(), inside closure_schedule()What happens:
run_in_terminal()returns a coroutine that is passed tocall_soon_threadsafe. The callback is scheduled, but the coroutine object itself is never awaited — it gets garbage collected by Python, triggeringRuntimeWarning: coroutine 'run_in_terminal.<locals>.run' was never awaited.Impact: When
_cprintfires from a background thread (e.g.,process_loop, voice auto-restart thread, self-improvement summaries), the printed text is silently dropped. The warning message"process_loop unhandled error (msg may be lost)"at line 12338 is the symptom of this.Trigger: Background thread calls
_cprintwhile the prompt_toolkit app loop is running on a different thread — specifically whenget_running_loop()succeeds and differs fromapp.loop.Bug B — Broken input prompt on WSL in
_prompt_text_input()(line 5876)File:
cli.py, function_prompt_text_input()What happens: On WSL terminals (Warp, PowerShell, etc.), the
run_in_terminal()coroutine is scheduled but the event loop fails silently to execute it. No exception propagates up, no fallback runs,result[0]staysNone.Impact: The input prompt never displays. User keystrokes go to the agent buffer instead of the confirmation prompt — making it appear the agent is consuming their input.
Root Cause
prompt_toolkit.application.run_in_terminal()(v3.0.52+):Every call site must either
awaitthe returned coroutine or wrap intry/exceptwith a direct fallback.Fix Summary
_cprint): Await the coroutine viaasyncio.ensure_future()inside the callback, or useloop.call_soon_threadsafewithasyncio.create_taskpattern._prompt_text_input): Addexcept Exception: _ask()afterrun_in_terminal(_ask)to provide the direct fallback when run_in_terminal fails on WSL.Environment:
prompt_toolkit>=3.0.52,<4