Skip to content

fix: forward SIGINT to child process for proper Ctrl+C handling#32

Merged
fdarian merged 9 commits intomainfrom
pm-ctrl-c-hanging
Mar 12, 2026
Merged

fix: forward SIGINT to child process for proper Ctrl+C handling#32
fdarian merged 9 commits intomainfrom
pm-ctrl-c-hanging

Conversation

@fdarian
Copy link
Copy Markdown
Owner

@fdarian fdarian commented Mar 12, 2026

Summary

  • Bypass @effect/platform's CommandExecutor (which uses detached: true) and spawn child processes directly via Node's child_process.spawn
  • On SIGINT, recursively walk the entire descendant process tree and send SIGINT to each process individually — handles multi-level trees where descendants (e.g. vite) create their own process groups
  • On fiber interruption, the finalizer kills the tree with SIGTERM

Why tree-kill instead of process group forwarding

The initial approach (process.kill(-pid, 'SIGINT')) only reaches processes in the child's immediate process group. In practice, deep descendants like vite create their own process groups (observed: pnpm/bun in PGID X, vite in PGID Y), so they never receive the forwarded signal. Tree-kill (pgrep -P recursion) finds and signals every descendant regardless of process group.

Test plan

  • pm dev in a project with vite — Ctrl+C — no orphaned processes (lsof -i :3000 clean)
  • pm i with Ctrl+C during install — clean exit
  • pm run <script> with other long-running scripts

fdarian added 9 commits March 12, 2026 09:39
When running shell commands, the spawned child process is in a separate
process group (due to detached: true). This causes Ctrl+C to not properly
terminate the child. Forward SIGINT signals to the child's process group.
The previous approach (forwarding SIGINT to child's process group via
-pid) didn't work because deep descendants like vite create their own
process groups. Now recursively walks the process tree and sends SIGINT
to every descendant individually.
Pipe stdout/stderr (instead of inherit) so we can unpipe before killing
the child tree. This suppresses pnpm's "ELIFECYCLE Command failed."
message that appears when its child exits non-zero during shutdown.
…ss groups

- Use stdio: 'inherit' to preserve colors, escape codes, and terminal clearing
- On Ctrl+C, only SIGTERM descendants in different process groups (e.g. vite)
  while leaving the PM's group (pnpm/bun) untouched
- This lets bun handle SIGINT naturally (prints "Shutting down", kills vite,
  exits 0) and pnpm sees a clean exit (no ELIFECYCLE)
- Effect.uninterruptible prevents the runtime from racing to kill children
@fdarian fdarian merged commit c3b5e53 into main Mar 12, 2026
1 check passed
@fdarian fdarian deleted the pm-ctrl-c-hanging branch March 12, 2026 14:17
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.

1 participant