Stop streaming commands cleanly on SIGTERM, like Ctrl-C#194
Merged
Conversation
The realtime commands treat Ctrl-C (SIGINT -> KeyboardInterrupt) as a clean "user stopped" signal that flushes closing state and exits 0. An external supervisor that stops the command from outside the terminal -- a Hammerspoon hotkey, a service manager, a wrapper script's `kill` -- sends SIGTERM, whose default action aborts the process before that flush runs, and delivering SIGINT instead means signalling the whole process group under a shell wrapper. Add `core.signals.terminate_as_interrupt`, a context manager that re-raises SIGTERM as KeyboardInterrupt (main-thread-only, restores the prior handler), and wrap the interactive run of `stream` (single + --from-stdin batch), `agent`, `agent-cascade`, and `speak` with it. SIGTERM now routes through each command's existing clean-stop path, so a controller can stop a recording with a plain SIGTERM. No new flag; behavior is transparent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Make
stream,agent,agent-cascade, andspeaktreat SIGTERM the same as Ctrl-C — a clean stop that flushes closing state and exits 0.Why
The realtime commands already treat Ctrl-C (SIGINT →
KeyboardInterrupt) as a graceful "user stopped" signal. But an external supervisor that stops the command from outside the terminal — a Hammerspoon hotkey, a service manager, a wrapper script'skill— sends SIGTERM, whose default action aborts the process before that flush runs. Delivering SIGINT instead is awkward: under ajust/shell wrapper it means signalling the whole process group.This came out of wiring
assembly streamto a Hammerspoon start/stop hotkey: stopping cleanly required perlsetpgrp+kill -INT -<pgid>gymnastics purely to deliver a clean SIGINT. With this change a controller just sends SIGTERM (e.g. Hammerspoon'shs.task:terminate()).How
aai_cli/core/signals.py—terminate_as_interrupt()context manager: installs a SIGTERM handler that raisesKeyboardInterrupt, so SIGTERM flows through each command's existing clean-stop path. Main-thread-only (no-op off-thread, wheresignal.signalis forbidden) and always restores the previous handler.stream(single source +--from-stdinbatch),agent,agent-cascade,speak.No new flag; the behavior is transparent. SIGTERM mirrors whatever Ctrl-C already does per command (exit 0 for stream/agent/cascade; the same abort path for speak).
Tests
tests/test_signals.py— handler install/restore + off-main-thread no-op.tests/test_stream_sigterm.py— both stream paths install the handler around the run.Full
scripts/check.shgreen locally (100% patch coverage, mutation gate passed).🤖 Generated with Claude Code