Repository navigation
fix(cli): a killed CLI no longer leaves its child processes running - #6431
Merged
Merged
Conversation
Contributor
Edit accuracy: accurate 271 (base branch 271), smooth 165 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (3)
|
Contributor
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head 302e2aa9
Scope
- Re-pinned from
9eb4a39cto302e2aa9. The only added change is a two-line fixture comment. - Reaper: node pipes are CLOEXEC, so no child inherits the write end. The hold is released on
close, so a normal run kills nothing. The-escript is CJS, and it works under both node and bun: tested directly with a detachedsleepand a SIGKILLed parent, and the sleeper died in both runtimes.orphanCleanupmatches only the Chrome marker, so it can't kill the reaper. pipe: true: nothing in engine, producer, cli, lambda or cloud-run readswsEndpointor callsconnect, and the oldsigintLaunchOptionsname has no other users.- Run locally:
childReaper.test.tspasses 3/3, and it fails withcancellableProcess.tsreverted to main.chromeOrphans.process.test.tspasses 3/3, and the mid-render case fails withpipe: trueremoved. - CI on this head is all green, Windows render included.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 26, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 26, 2026
SanHsien
added a commit
to SanHsien/hyperframes
that referenced
this pull request
Oct 26, 2026
Brings in 10 upstream commits (ad4d3fa..15367a9): heygen-com#6425, heygen-com#6427, heygen-com#6429, heygen-com#6431, heygen-com#6436, heygen-com#6432, heygen-com#6453, heygen-com#6457, heygen-com#6450, heygen-com#6451. One conflict, packages/core/src/compiler/compositionScoping.ts (heygen-com#6436). This fork passes authored composition code to Function() as a JSON string literal, so upstream's markers around the raw source would sit outside the text its page-side scan reads and the fix would silently not apply: the wrapper's own __timelines use would make every composition look able to register a timeline. The markers go inside the literal instead, begin marker on the first source line so error line numbers do not shift. The pageBuild goldens change only by those two marker comments. A new engine test runs upstream's scan on this fork's real wrapper output and fails without the markers. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SanHsien
added a commit
to SanHsien/hyperframes
that referenced
this pull request
Oct 26, 2026
…d PR watermark to heygen-com#6459 Commit watermark 8f618c5; PR watermark heygen-com#6459 only, because heygen-com#6460 onward and issue heygen-com#6465 were not reviewed this round (listed in DECISIONS). Issue watermark stays heygen-com#6443. Records the heygen-com#6436 Function() wrapper port, the chromeOrphans failure that is not caused by heygen-com#6431, and the environment-only test failures. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
What
A CLI that dies outright (SIGKILL, out of memory, a crash) no longer leaves its child processes running. Every process it started through
runCancellableProcess, and the Chrome a render launches, now ends with it.Why
Repro on Linux, on
main:hyperframes rendera 20 s project, andkill -9the CLI after 10 s. All fivechrome-headless-shellprocesses keep running, re-parented to init, in their own process group.hyperframes previewon a large project, andkill -9the server while it lints. The lint worker keeps running until it finishes: two of them ran for 12 minutes at about 80% CPU on a 9.6 MB composition.A clean exit was already fine: the CLI's signal handlers stop the setup worker's process tree, and
closeManagedBrowserstops Chrome. Nothing covered a process that cannot run a handler.Two different gaps:
runCancellableProcessstarts children in their own process group on macOS and Linux, soterminateProcessTreecan stop a whole tree on cancel. That same choice takes them out of anything that would stop them when the CLI dies. A child cannot watch its own way out either: the lint worker parses synchronously, so no listener in it runs until the lint ends.cli/src/browser/launch.ts) already usespipe: true, where Chrome ends when the other end of its pipe closes, and that is whycheckalready passes the existing "killed mid busy-loop" test.How
utils/childReaper.ts: the firstrunCancellableProcesscall starts one small reaper process, detached, whose stdin is a pipe from the CLI. Each spawn sends+ <pid> g|p, each close sends- <pid>. When that pipe closes, because the CLI exited or died, the reaper kills whatever is still held: the whole process group for a detached child, the pid for an attached one (taskkill /T /Fon Windows). It is the stdin-close pattern ofbrowser/windowsChromeWatchdog.ts, held once per CLI process instead of once per Chrome.sigintLaunchOptions, renamedownedLaunchOptionssince it now carries both) addpipe: true, for every engine Chrome launch: render, the beginframe fallback, the GPU probe and HDR capture. Nothing connects to the engine's Chrome by websocket (nowsEndpointorconnectuse in engine, producer or cli).Options considered for the non-node children and the busy worker:
What changes in output
Nothing for a run that ends normally. After a hard kill, no
chrome-headless-shell, lint worker or probe is left running. The orphan sweep at the next CLI start (orphanCleanup.ts) still runs; it now finds nothing to clean in these cases.Test plan
Unit tests added/updated
Manual testing performed
Documentation updated (if applicable): not needed, no flag or output changes
Comments follow CONTRIBUTING.md "Comments": they say why, not what, and a bug fix says what the code must do and how to reproduce the bug
childReaper.test.ts: a fixture CLI starts a child throughrunCancellableProcessthat blocks its event loop like a synchronous lint and starts a grandchild. The test SIGKILLs the fixture and waits for both to be gone. Without the reaper it fails ("Condition not met within 5000 ms"). Passed 3 runs in a row.chromeOrphans.process.test.ts: "leaves no Chrome process behind when the CLI is killed mid-render" renders a 30 s film, SIGKILLs the CLI while Chrome is up, and waits for every Chrome process to be gone.Re-checked on a Linux box: the new tests and the engine
browserManagersuite pass 3 runs in a row. Removing the hold inrunCancellableProcessfailschildReaper.test.ts, and removingpipe: truefails the mid-render test, each alone.Does
pipe: truecost render time?Same 6 s, 30 fps, 1920x1080 GSAP composition (180 frames, beginframe capture), rendered with the built CLI on a Linux box,
mainand this branch interleaved, three runs each. The box was shared and busy (load 6 to 10 on 8 cores), so read the spread, not single runs.Wall time and pipeline total are not higher. Capture p50 reads 5 ms higher at the median, inside the spread
mainshows on its own runs (76 to 94 ms), and the mean per frame is not higher. I did not measure a difference outside that noise; a quiet-box rerun would tighten it.