Skip to content

fix(cli): kill the whole child tree when runCommandSilently times out - #2455

Closed
fengmk2 wants to merge 1 commit into
mainfrom
fix/silent-command-grandchild-hang
Closed

fix(cli): kill the whole child tree when runCommandSilently times out#2455
fengmk2 wants to merge 1 commit into
mainfrom
fix/silent-command-grandchild-hang

Conversation

@fengmk2

@fengmk2 fengmk2 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Bug

runCommandSilently resolves on the child's close event, but its timeout killed only the direct child. When arbitrary code run by the child spawned a grandchild that inherited the piped stdio, the SIGKILL left the grandchild holding the pipe, close never fired, and the caller hung forever.

vp migrate hits this through the config-compat worker: resolving a project's vite.config executes project code, and any lingering stdio-inheriting descendant (an esbuild-style service, a daemon) wedges the migration permanently under a frozen "Checking config compatibility" spinner, at 0% CPU after the initial burst. This likely explains previously observed vp migrate hangs at 0% CPU.

Fix

  • Spawn the child in its own process group on POSIX and kill the group on timeout, so the whole tree dies with it.
  • Destroy our pipe ends after the kill, so close always fires even when a survivor holds the far end (the Windows path and escaped-group case). The timeout path rejects without reading output, so nothing is lost.

Test

The new case wedges a child that hands its pipes to a short-lived grandchild. Before the fix the promise never settled and the test died on its own timeout; with the fix the suite passes in under a second. The compat-runner spec passes unchanged.

runCommandSilently resolves on the child's close event, but the timeout
killed only the direct child. When arbitrary code run by the child (a
migration config worker executing a project's vite config) spawned a
grandchild that inherited the piped stdio, the SIGKILL left the
grandchild holding the pipe, close never fired, and the caller hung
forever. vp migrate showed this as a permanent 'Checking config
compatibility' spinner after a CPU burst.

Spawn the child in its own process group on POSIX and kill the group on
timeout, then destroy our pipe ends so close always fires even when a
survivor holds the far end (the Windows path and escaped-group case).
The timeout path rejects without reading output, so nothing is lost.

The new test wedges a child that hands its pipes to a 15s grandchild;
before the fix the promise never settled.
@netlify

netlify Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 9e92d97
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a7f1e34c73c5d000873199f

@fengmk2

fengmk2 commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@codex review

@fengmk2 fengmk2 closed this Aug 14, 2026
@fengmk2
fengmk2 deleted the fix/silent-command-grandchild-hang branch August 14, 2026 14:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e92d970dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

env: options.envs,
// Own process group (POSIX) so the timeout can kill the whole tree: the
// child runs arbitrary project code that may spawn its own children.
detached: process.platform !== 'win32',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detach only commands that have a timeout

On POSIX this places every silent command in a new session, including the no-timeout install, format, and migration commands. Consequently, Ctrl-C signals the foreground process group containing vp but not the detached child; because this runner has no shutdown handler that forwards the signal, a package installation or build script can continue modifying the project after the user cancels the CLI. Limit detached to timeout-bearing invocations (or explicitly terminate the child during parent shutdown) so ordinary commands retain signal propagation.

Useful? React with 👍 / 👎.

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