Repository navigation
fix(devbox): wait for the command to exit when devbox run is interrupted - #3033
Conversation
devbox never handled SIGINT, so Ctrl-C killed it right away while the command it started (which gets the same SIGINT from the terminal) was still shutting down. The shell prompt came back early and the command's shutdown output landed after it. RunScript now catches SIGINT and SIGQUIT while the command runs. If devbox is in the terminal's foreground process group, the command got the signal too, so devbox just waits for it. Otherwise (e.g. kill -INT <devbox pid> without a terminal) it falls back to the default action and exits as before. Signals that were already ignored are left alone so the command still inherits the ignore. Since devbox now outlives an interrupted command, report signal deaths the way a shell does (128+N, so 130 for Ctrl-C instead of 255) and skip the "Error: ... signal: interrupt" message. Fixes #3026 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
|
There is an old issue I filed that this seems related to: #1815 |
|
@kadaan I think it does, but some reports in that thread are hard to repo, so I don't know if it fixes everything. I'm inclined to close both issues once this is merged |
|
@claude ptal |
|
Claude finished @mikeland73's task in 11s —— View job I'm not sure what "patal" means. It may be a typo, or the rest of the message may have been cut off. I haven't changed anything on the branch. What would you like me to do on this PR? For example:
Reply with |
|
Claude finished @mikeland73's task in 38s —— View job Review of #3033I read the full diff. I did not run the build or tests in this session. Nothing blocks the merge, and I have a few small notes. What looks right
Notes (non-blocking)
Overall this looks good to merge. |
Summary
Fixes #3026. Pressing Ctrl-C during
devbox runbrought the shell prompt back right away, while the command was still shutting down and printing output. Devbox has never handled SIGINT (nothing in the repo importedos/signal), so this isn't a regression. The terminal sends SIGINT to the whole foreground process group, so devbox died immediately while the command handled the signal on its own schedule.Changes:
nix.RunScriptcatches SIGINT and SIGQUIT while the command runs.tcgetpgrp(/dev/tty) == getpgrp()), the command already got the signal from the terminal, so devbox waits for it to exit. It deliberately does not forward the signal: a second SIGINT makes servers like uvicorn/FastAPI force-quit.kill -INT <devbox pid>with no terminal), devbox resets the handler and re-raises the signal, so it exits exactly as before.signal.Notifywould otherwise un-ignore them for devbox and for the command (e.g.devbox run x &in a script).usererr.ExitError.ExitCodereports a signal death as 128+N, the way a shell does. Now that devbox outlives an interrupted command, it would otherwise exit 255 instead of the 130 it gets today.midcobradebug middleware no longer printsError: error running script "x" in Devbox: signal: interruptafter Ctrl-C. Devbox used to die before it could print this, so output stays as it was.Out of scope: devbox still doesn't forward SIGTERM (e.g.
docker stop). Doing that properly means signalling the command's process group, since real scripts run assh -c script.sh, and that has its own trade-offs.How was it tested?
TestRunScriptWaitsForCommandOnCtrlC: the test binary re-runs itself as a helper under a pty (viacreack/pty, already in the module graph) and runs a script that traps INT and takes 0.5s to shut down. The test sends^Cand checks that RunScript returns only after the script finishes, with its exit code. It fails onmain(signal: interrupt) and passes here, including with-race.TestExitErrorExitCode(exit 3 → 3, SIGINT → 130 +Interrupted(), SIGTERM → 143).^C server: got INT, thenPROMPT BACK, thenserver: shutdown complete.^C server: got INT, thenserver: shutdown complete, thenPROMPT BACK (devbox exit 0).sleep 30script exits 130 with no error message.kill -INT <devbox pid>still kills devbox immediately, the same as 0.19.0. With SIGINT ignored by the parent (trap '' INT), the command still ignores it, the same as 0.19.0.go test -race ./internal/nix ./internal/boxcli/... ./internal/devboxpasses, as do therun-related testscripts. Lint is clean.go mod vendoroutput is unchanged (only thex/sysandcreack/ptyindirect markers moved), sovendor-hashdoesn't need updating.Community Contribution License
All community contributions in this pull request are licensed to the project
maintainers under the terms of the
Apache 2 License.
By creating this pull request, I represent that I have the right to license the
contributions to the project maintainers under the Apache 2 License as stated in
the
Community Contribution License.
🤖 Generated with Claude Code