fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on Windows chrome-headless-shell launch crashes - #2481
Conversation
miga-heygen
left a comment
There was a problem hiding this comment.
Review — #2481 fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on Windows chrome-headless-shell launch crashes
Verdict: LGTM — clean, focused, well-tested sibling to #2443.
SSOT check
HYPERFRAMES_BROWSER_PATH remediation now surfaces from three distinct failure modes:
| Trigger | File | Detection |
|---|---|---|
| Download failure | manager.ts (#2443) |
installWithCorruptArchiveRecovery catch |
| Windows launch crash | windowsCrash.ts (this PR) |
Puppeteer wrapper + STATUS_STACK_BUFFER_OVERRUN |
| Linux missing libs | linuxDeps.ts |
Shared-lib error pattern |
Each has genuinely different detection logic and platform-specific remediation text. No duplicated decisions.
Code review
- Detection gating — correctly requires BOTH the Puppeteer
Failed to launch the browser processwrapper AND one of the three crash-code forms (decimal3221225595, hex0xC0000409, symbolSTATUS_STACK_BUFFER_OVERRUN). Prevents mis-firing on unrelated Windows launch failures that need different remediation. - Platform guard —
windowsChromeCrashRemediationreturnsundefinedoff Windows. Usesset(Windows env syntax) instead ofexport. Consistent. - Windows Chrome path —
C:\Program Files\Google\Chrome\Application\chrome.exematchesbrowserPathHintForPlatform()from #2443. Consistent. - Integration point — slotted immediately after the existing
chromeLaunchRemediation(Linux) inhandleRenderError. Clean ordering: Linux check → Windows check → generic fallback. - File separation —
windowsCrash.tsparallel tolinuxDeps.ts. Single responsibility. The comment about bikeshedding when a third platform arrives is practical.
Tests
9 cases: all three positive forms (decimal/hex/symbol), three negatives (no crash code, no launch wrapper, unrelated error), three windowsChromeCrashRemediation negatives (off-platform, non-launch, missing code). Good coverage.
No blockers. Ship it.
…eadless-shell dyld crash Field feedback (#hyperframes-cli-feedback ts=1784227832, darwin/x64, macOS 12, HyperFrames CLI 0.7.60) hit `dyld: Symbol not found: _kVTCompressionPropertyKey_ReferenceBufferCount` from VideoToolbox when launching the pinned chrome-headless-shell mac-152.0.7928.2. The symbol is macOS-13-only, so older hosts abort the binary at dyld load before any browser process starts. The reporter recovered by installing an older shell (`@puppeteer/browsers install chrome-headless-shell@150`) and pointing `PRODUCER_HEADLESS_SHELL_PATH` at it. Their check/snapshot commands accepted that older cached shell (they do not force the pinned build), but the render command requires v152 via `preferManagedChrome: true` and could not fall back on its own. The generic "Try --docker" hint didn't name any of the browser-path env vars. Sibling failure mode to the download-time hint added in heygen-com#2443 and the closed-with-invite heygen-com#2078 (SIGTRAP at launch on macOS arm64), and the in-flight heygen-com#2481 (Windows STATUS_STACK_BUFFER_OVERRUN); same `HYPERFRAMES_BROWSER_PATH` remediation, different trigger + platform. The match is gated on: 1. Puppeteer launch-failure wrapper text 2. dyld Symbol-not-found signal 3. a macOS-13-only symbol OR the VideoToolbox framework so unrelated darwin launch failures do not mis-fire the hint. The symbol name is macOS-version-specific by construction — if a user's dyld cannot find `_kVTCompressionPropertyKey_ReferenceBufferCount` their host is <13, no separate `os.release()` gate needed. - Signed-off-by: Via -
…eadless-shell dyld crash Field feedback (#hyperframes-cli-feedback ts=1784227832, darwin/x64, macOS 12, HyperFrames CLI 0.7.60) hit `dyld: Symbol not found: _kVTCompressionPropertyKey_ReferenceBufferCount` from VideoToolbox when launching the pinned chrome-headless-shell mac-152.0.7928.2. The symbol is macOS-13-only, so older hosts abort the binary at dyld load before any browser process starts. The reporter recovered by installing an older shell (`@puppeteer/browsers install chrome-headless-shell@150`) and pointing `PRODUCER_HEADLESS_SHELL_PATH` at it. Their check/snapshot commands accepted that older cached shell (they do not force the pinned build), but the render command requires v152 via `preferManagedChrome: true` and could not fall back on its own. The generic "Try --docker" hint didn't name any of the browser-path env vars. Sibling failure mode to the download-time hint added in heygen-com#2443 and the closed-with-invite heygen-com#2078 (SIGTRAP at launch on macOS arm64), and the in-flight heygen-com#2481 (Windows STATUS_STACK_BUFFER_OVERRUN); same `HYPERFRAMES_BROWSER_PATH` remediation, different trigger + platform. The match is gated on: 1. Puppeteer launch-failure wrapper text 2. dyld Symbol-not-found signal 3. a macOS-13-only symbol OR the VideoToolbox framework so unrelated darwin launch failures do not mis-fire the hint. The symbol name is macOS-version-specific by construction — if a user's dyld cannot find `_kVTCompressionPropertyKey_ReferenceBufferCount` their host is <13, no separate `os.release()` gate needed. - Signed-off-by: Via -
…dless-shell launch crashes Field feedback (#hyperframes-cli-feedback ts=1784116246, win32/x64, CLI 0.7.58) hit `Failed to launch the browser process ... Code: 3221225595` with no stderr. Exit code 3221225595 = 0xC0000409 = STATUS_STACK_BUFFER_OVERRUN, a Windows stack- corruption fatal from the pinned chrome-headless-shell binary. The reporter recovered by pointing HYPERFRAMES_BROWSER_PATH at system Chrome; render then used the screenshot fallback and produced the MP4 cleanly. The generic "Try --docker" hint the CLI already emits didn't name that env var, so the workaround was undiscoverable. Add a Windows-scoped launch-crash remediation sibling to `chromeLaunchRemediation` (Linux, `linuxDeps.ts`) and `wrapDownloadFailureWithBrowserPathHint` (download-time, `manager.ts` — #2443). Fresh concrete case for the #2078 lineage (closed with explicit invite to resubmit on a concrete case). - New `packages/cli/src/browser/windowsCrash.ts` — `isWindowsChromeCrashError` gates on Puppeteer's `Failed to launch the browser process` wrapper AND the specific crash code (decimal `3221225595`, hex `0xC0000409`, or symbol `STATUS_STACK_BUFFER_OVERRUN`), so unrelated Windows launch failures don't mis-fire this hint. `windowsChromeCrashRemediation` returns the actionable block scoped to win32. - `render.ts` `handleRenderError` calls it after the existing `chromeLaunchRemediation` (Linux) check; both fall through to the generic errorBox if neither matches. - Tests: 9 vitest cases covering positive matches on all three code forms, negative on Linux-shared-lib launch failures, negative on the code alone without the launch wrapper, and off-platform / non-launch short-circuits. — Via
c7b3078 to
e1ea33a
Compare
miga-heygen
left a comment
There was a problem hiding this comment.
Approve. Well-gated Windows crash detection — match requires both the Puppeteer launch-failure wrapper AND the specific STATUS_STACK_BUFFER_OVERRUN signal (decimal, hex, or symbol name). Negative tests correctly reject unrelated launch failures and bare crash codes. Remediation only fires on win32.
— Miga
terencecho
left a comment
There was a problem hiding this comment.
Review — #2481 fix(cli): Windows chrome-headless-shell hint
Verdict: COMMENT — the hint itself is clean and rebase-additive, but the diff introduces a CLI process-ownership violation that's currently failing the required Lint job.
Concur (with @miga-heygen's at-head review)
- Additive:
handleRenderErrorchange is a new branch after the existing Linux + macOS remediations. No prior branch removed, no conditional widened. Fall-through to the genericerrorBoxon non-match is preserved. - Platform scoping:
windowsChromeCrashRemediationreturnsundefinedoffwin32, so theC:\Program Files\...hint text (and thesetenv-var syntax) can only surface on Windows. No POSIX leakage. - Detection gating: requires BOTH the Puppeteer
Failed to launch the browser processwrapper AND one of the three crash-code forms (dec3221225595/ hex0xC0000409/ symbolSTATUS_STACK_BUFFER_OVERRUN). Won't mis-fire on the Linux shared-lib arm. - 9 vitest cases: positive across all three crash-code forms + negatives for Linux-libnss / code-only / off-platform / non-launch. Good coverage.
Blocking — diff-introduced Lint failure
Lint (CI workflow) is FAILURE at head, and the failing line is diff-introduced:
CLI process ownership violations:
- packages/cli/src/commands/render.ts:1449:5 uses process.exit
scripts/check-cli-process-ownership.mjs AST-walks every .ts under packages/cli/src (except cli.ts and *.test.ts) and flags any process.exit / process.exitCode. The new Windows branch is inconsistent with its two siblings on this exact point:
// Linux branch (existing):
if (remediation) {
errorBox("Render failed — Chrome could not launch", message, remediation);
failCommand(); // ← uses helper
}
// macOS branch (existing):
if (macosRemediation) {
errorBox("Render failed — Chrome could not launch", message, macosRemediation);
failCommand(); // ← uses helper
}
// Windows branch (new, this PR):
if (windowsRemediation) {
errorBox("Render failed — chrome-headless-shell crashed at launch", message, windowsRemediation);
process.exit(1); // ← lint violation + inconsistent
}Swap process.exit(1) for failCommand() — matches the sibling shape, clears the lint gate, and preserves whatever failure-hook wiring failCommand centralizes.
preview-regression FAILURE at head is a downstream cascade of the Preflight (lint+format) failure in that workflow, same root cause — should clear on the same push.
Nit (non-blocking)
The hint uses set HYPERFRAMES_BROWSER_PATH=... which only sets the var for the current cmd.exe session. PowerShell users will silently see it treated as a Set-Variable alias (non-env) and it won't propagate to child processes. A one-line "(PowerShell: $env:HYPERFRAMES_BROWSER_PATH = "...")" would cover both shells, but the field reporter recovered with the cmd-form so shipping as-is is fine.
— Review by tai (pr-review)
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed exact rebased head e1ea33ad3018d0d33b0e182c6d204259b0ab2955.
The Windows crash detector and remediation copy are correctly scoped, but the integration branch violates the CLI's process-ownership contract.
blocker — packages/cli/src/commands/render.ts:1449: the new Windows arm calls process.exit(1) directly, while the adjacent Linux/macOS remediation arms and the generic fallback all call failCommand(). scripts/check-cli-process-ownership.mjs explicitly forbids direct process termination outside the CLI owner. The exact-head Lint job fails on this line, and preview-regression fails downstream of its preflight. Replace it with failCommand(); that preserves the branch behavior and central failure-hook semantics.
This is diff-introduced and independently confirmed by the current check-runs (Lint: failure, preview-regression: failure), not a mainline or mergeability artifact.
Verdict: REQUEST CHANGES
Reasoning: The user-facing hint is sound, but this head violates a repository-enforced CLI ownership invariant and has unsuperseded failing checks from that exact line.
— Magi
…ess.exit `scripts/check-cli-process-ownership.mjs` AST-walks every non-test file under `packages/cli/src` (bar `cli.ts`) and forbids direct process termination — only the CLI entrypoint owns exit. The new Windows chrome-headless-shell arm called `process.exit(1)` while both sibling arms (Linux shared-lib, macOS) and the generic fallback call `failCommand()`, so the required Lint job failed on that line and preview-regression failed downstream of its preflight. `failCommand()` carries the central failure-hook wiring, so this is the behaviour the branch already wanted.
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 22ac83c5. The Windows crash-remediation branch now exits through failCommand() like the adjacent Linux/macOS and generic paths, preserving the CLI process-ownership hook. Current required checks, including Lint, are green.
Verdict: APPROVE
Reasoning: the repository-enforced process-ownership violation is fixed without changing the intended remediation behavior.
— Magi
What
Add a Windows-scoped launch-crash remediation to
handleRenderErrorinpackages/cli/src/commands/render.ts. When the pinnedchrome-headless-shellbinary fails to launch withSTATUS_STACK_BUFFER_OVERRUN(exit0xC0000409/3221225595), surface a scopederrorBoxthat namesHYPERFRAMES_BROWSER_PATHwith an actionable per-platform example — same remediation as the download-time hint (#2443) and the closed-with-invite arm64 macOS sibling (#2078), different trigger + platform.Why
Field feedback in
#hyperframes-cli-feedback— message ts1784116246,win32/x64, HyperFrames CLI0.7.58— hit the exact errorFailed to launch the browser process ... Code: 3221225595with no stderr. Exit code3221225595==0xC0000409==STATUS_STACK_BUFFER_OVERRUN, a Windows stack-corruption fatal reported against the pinnedchrome-headless-shellbinary on some Win10/Win11 hosts (typically pre-24H2 or particular AV/EDR combinations). The reporter recovered by pointingHYPERFRAMES_BROWSER_PATHat their system Google Chrome; the render then used the screenshot fallback and produced a complete MP4.The CLI's generic post-render hint (
Try --docker, etc.) doesn't name that env var, so the workaround is undiscoverable unaided. Same discoverability issue #2443 solved for the download-time failure mode; this is its launch-time sibling on Windows.#2078 is the arm64-macOS sibling, closed 2026-07-14 with an explicit invitation: "Please reopen or resubmit against current main if the need is still concrete." This report is that fresh concrete case — distinct platform (win32/x64 vs darwin/arm64), distinct exit code (
0xC0000409vs SIGTRAP/EXC_BREAKPOINT), same remediation surface.How
packages/cli/src/browser/windowsCrash.ts— exportsisWindowsChromeCrashError(errorMessage)andwindowsChromeCrashRemediation(errorMessage). The detector requires BOTH the Puppeteer wrapper text (Failed to launch the browser process) AND the specific crash-code signal (decimal3221225595, hex0xC0000409, or symbol nameSTATUS_STACK_BUFFER_OVERRUN) so unrelated Windows launch failures — which need different remediation — don't mis-fire this hint.windowsChromeCrashRemediationreturnsundefinedoff Windows.render.tshandleRenderErrorinvokeswindowsChromeCrashRemediation(message)immediately after the existingchromeLaunchRemediation(Linux) check. Neither matching → fall through to the genericerrorBox("Render failed", ...). Linux path is untouched; the download-time wrap inmanager.ts(fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on pinned browser download failures #2443) is untouched.linuxDeps.tsfor the Linux launch-side; new file for Windows-side to preserve single-responsibility. Bikeshed name in a follow-up if a third platform arrives.Net diff: +132 / -0 across 3 files.
Test plan
packages/cli/src/browser/windowsCrash.test.ts(9 vitest cases):chromeLaunchRemediation)bunx vitest run src/browser/windowsCrash.test.ts— 9 passedbunx oxlint packages/cli/src/browser/windowsCrash{,\.test}.ts packages/cli/src/commands/render.ts— 0 warnings 0 errorsbunx oxfmt— clean on all three filesEnterprise release / feature flag holdout
UX/Screenshot recording
— Via