Repository navigation
Fix Ctrl+C handling in CLI - #4411
Merged
Merged
Conversation
confirm_ask turned Ctrl+C into sys.exit(1), so handlers that catch a second Ctrl+C at a prompt never ran: `dstack apply` didn't abort the run, and preset creation left the agent and its runs going. Handle KeyboardInterrupt once in main(): print "Interrupted" and re-raise with the traceback hidden. On an uncaught KeyboardInterrupt, Python shuts down normally and then kills itself with SIGINT, so a calling shell script stops too. Exiting with 130 doesn't do that: bash assumes the child handled SIGINT itself and continues. - Drop the interrupt handlers in confirm_ask, login, _repo_diff_verbose (library code), and the apply and preset catch-alls, which exited 0. - Re-raise after aborting a run on a second Ctrl+C. - Let Ctrl+C through questionary menus (unsafe_ask). - Respect -y when interrupting fleet provisioning on old servers. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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.
confirm_askturned Ctrl+C intosys.exit(1). Callers that catch a second Ctrl+C at their prompt never saw it:dstack apply: Ctrl+C at "Stop the run before detaching?" is meant to abort the run. Instead it printed "Cancelled by user", detached and left the run running.dstack applywith a preset: Ctrl+C at "Stop creating preset?" is meant to stop it. Instead the agent (started in its own session) kept running along with its runs.Besides that, Ctrl+C was handled inconsistently. Some commands exited 0 (
apply,preset), some exited 1, and the rest printed a traceback.Changes
main()handlesKeyboardInterruptonce. It prints "Interrupted" to stderr and re-raises, with the traceback hidden by an excepthook. On an uncaughtKeyboardInterrupt, Python shuts down normally (includingatexit, which closes SSH tunnels) and then kills itself with SIGINT. A calling shell script sees that and stops. Exiting with 130 is not the same: bash then assumes the child handled SIGINT itself and continues the script.confirm_ask,dstack login,_repo_diff_verbose(library code, also reachable through the Python API), and theapplyandpresetcatch-alls that exited 0.dstack apply: after aborting a run on a second Ctrl+C, re-raise instead of exiting 0.dstack loginprovider, default project) useunsafe_ask().ask()swallows Ctrl+C and returnsNone, sodstack logincarried on as if nothing had been selected.-y, matching the new-server path.Deliberate stops still exit 0: detaching from a run, the y/n answers at the stop prompts,
--watchloops,dstack logs,dstack server(uvicorn swallows the interrupt itself), and the preset commands after their own interrupt handling.Before/after
Ctrl+C at the
dstack loginURL prompt, insidefor i in 1 2; do dstack login; echo "iteration $i: rc=$?"; done:Before:
After (the loop stops; bash itself exits via SIGINT):
Tests
dstack applyand preset double-Ctrl+C tests drive the realconfirm_askand fail without the fix.test_main.pycovers the handler and the excepthook. It also has one subprocess test (~0.6 s, POSIX only) checking that the process exits via SIGINT without a traceback.🤖 Generated with Claude Code