Skip to content

Ignore SIGINT in parent during child command execution#2230

Open
joaquinhuigomez wants to merge 8 commits into
pypa:masterfrom
joaquinhuigomez:fix/sigint-handling-2228
Open

Ignore SIGINT in parent during child command execution#2230
joaquinhuigomez wants to merge 8 commits into
pypa:masterfrom
joaquinhuigomez:fix/sigint-handling-2228

Conversation

@joaquinhuigomez
Copy link
Copy Markdown

Fixes #2228. When hatch run executes a child command, the parent process now ignores SIGINT so that only the child handles Ctrl-C. Previously the terminal's SIGINT reached both processes, causing hatch to abort with KeyboardInterrupt even when the child (e.g. a Python REPL or Jupyter) intended to stay alive. The original handler is restored after each command completes.

@cjames23
Copy link
Copy Markdown
Member

The core idea is right — ignoring SIGINT in the parent while the child runs is the standard POSIX shell pattern for foreground jobs. A few things to consider:

Two subprocess paths in platform.run_command run_shell_command delegates to platform.run_command, which has two code paths: a direct subprocess.run call, and _run_command_integrated which uses Popen with a manual output-streaming loop. The SIG_IGN approach happens to work for both, but the Popen path deserves an explicit comment or test since its behavior under ignored SIGINT depends on Popen.exit calling wait() (no exception propagated) rather than kill() + wait() (exception propagated). If that assumption changes upstream, this breaks silently.

Consider the multi-command loop The fix wraps the individual run_shell_command call, which is correct — between commands in a sequence the user should still be able to Ctrl-C to abort. Worth adding a brief comment explaining this is intentional, since a future reader might be tempted to hoist the SIG_IGN around the entire loop for "simplicity."

Plugin overrides run_shell_command is a plugin hook — third-party environment plugins (Docker, SSH, etc.) can override it and may spawn processes outside the local process group. The SIG_IGN on the parent won't help those cases. Not a blocker, but worth a note in the docstring or a comment so plugin authors know the expectation.

@joaquinhuigomez
Copy link
Copy Markdown
Author

Providing the rationale to facilitate the merge

SIGINT handling: Ignore in the parent for the duration of this single child command, then restore. Two constraints future readers must preserve:

  1. The signal.signal (SIGINT, original_handler) restore call MUST stay inside finally. If the child path raises or is cancelled and we restored the handler only on the success path, the parent would remain permanently deaf to Ctrl-C for the rest of the hatch session. This would silently breakevery subsequent command. Do not lift this out of finally under any refactor.

  2. This block is intentionally scoped per-command, not hoisted around the surrounding multi-command loop. Between commands in a sequence the user should still be able to abort with Ctrl-C.

@cjames23
Copy link
Copy Markdown
Member

cjames23 commented May 5, 2026

Yes I just want to have you add a comment to make sure that it is clear not to hoist the restore call outside of the finally block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hatch run handles keyboard interrupt (but maybe should defer to child)

2 participants