fix(cli): propagate exit signal callbacks#5888
Conversation
|
|
||
|
|
||
| class _ExitCli(BaseException): | ||
| class _ExitCli(KeyboardInterrupt): |
There was a problem hiding this comment.
π΄ _ExitCli swallowed in console text mode due to except KeyboardInterrupt now catching it
Changing _ExitCli's base class from BaseException to KeyboardInterrupt causes _ExitCli to be caught by the except KeyboardInterrupt: break handler in _text_mode (cli.py:1181). Previously, _ExitCli(BaseException) would bypass that handler and propagate up to the except _ExitCli: pass in _run_console (cli.py:1563), triggering a clean shutdown. Now, _ExitCli(KeyboardInterrupt) is caught at line 1181, _text_mode returns normally, and the while True loop at cli.py:1553 simply restarts text mode. The first SIGTERM/SIGINT in console text mode is silently swallowed instead of initiating shutdown. Moreover, exit_triggered is already True after the first signal, so subsequent signals call console_worker.shutdown() without raising, leaving the main thread stuck in the prompt() β readkey() blocking call.
Prompt for agents
The root cause is that _text_mode (cli.py:1168) catches KeyboardInterrupt at line 1181, and _ExitCli is now a subclass of KeyboardInterrupt, so _ExitCli raised from the signal handler gets caught there instead of propagating to the outer except _ExitCli handler in _run_console (line 1563).
To fix this, the except KeyboardInterrupt handler in _text_mode needs to distinguish between a native KeyboardInterrupt (raised by readkey() on Ctrl+C, see readchar.py:201) and _ExitCli. The simplest fix is to re-raise _ExitCli:
In _text_mode (around line 1181), change:
except KeyboardInterrupt:
break
to:
except _ExitCli:
raise
except KeyboardInterrupt:
break
This ensures _ExitCli propagates out to _run_console while still letting the native KeyboardInterrupt from readkey() break the text input loop.
Was this helpful? React with π or π to provide feedback.
There was a problem hiding this comment.
@he-yufeng thanks for the pr! I think this is a real issue, maybe re-raise the _ExitCli in _text_mode first.
try:
text = prompt(
Text.from_markup(" [bold]User input[/bold]: "),
console=c.console,
key_read_cb=_key_read,
placeholder="Type to talk to your agent",
)
except _ExitCli:
raise
except KeyboardInterrupt:
break
Summary
_ExitCliinherit fromKeyboardInterruptso asyncio callback frames re-raise it instead of routing it to the loop exception handler_ExitClioutsideException, preserving the previous fix for broadexcept ExceptionhandlersFixes #5856.
To verify
uv run pytest tests/test_drain_timeout.py -quv run ruff check livekit-agents\livekit\agents\cli\cli.py tests\test_drain_timeout.pyuv run ruff format --check livekit-agents\livekit\agents\cli\cli.py tests\test_drain_timeout.pyuv run python -m py_compile livekit-agents\livekit\agents\cli\cli.py tests\test_drain_timeout.pygit diff --check