fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on macOS <13 chrome-headless-shell dyld crash#2569
fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on macOS <13 chrome-headless-shell dyld crash#2569vanceingalls wants to merge 1 commit into
Conversation
…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 #2443 and the closed-with-invite #2078 (SIGTRAP at launch on macOS arm64), and the in-flight #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 -
miga-heygen
left a comment
There was a problem hiding this comment.
Review — fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on macOS <13 chrome-headless-shell dyld crash
Scope: 3 files (+182/−0) — new detector + remediation module, test file, one integration site in render.ts.
SSOT analysis
No violations. The detector is self-contained — no shared predicates with the Linux chromeLaunchRemediation. Different platforms, different error signals, different remediation content. The fallow-ignore-file code-duplication pragma is appropriate (structural similarity with the Linux detector, genuinely different content).
What I checked
-
Detector logic. Three-gate AND chain:
Failed to launch the browser process→Symbol not found:→ (_kVTCompressionPropertyKey_ReferenceBufferCountORVideoToolbox). The first two gates prevent false positives from unrelated errors; the third scopes to the macOS-13+ framework family. The OR on the third gate is intentional — future pinned builds may hit sibling VT symbols from the same framework, and the remediation (install an older shell) is the same for all of them. -
Platform gating.
isMacosOldChromeCrashErroris pure string matching (testable anywhere).macosOldChromeCrashRemediationgates onprocess.platform !== "darwin". Clean separation — the detector is logic, the remediation is action. -
Integration cascade. In
handleRenderError: Linux check → macOS check → generic fallback. Each detector is self-scoping (Linux won't fire on darwin, macOS won't fire on Linux), so the order is for readability, not correctness. Neither matching → genericerrorBox("Render failed", ...). -
Test coverage. 11 cases:
- Positive: exact reporter error, VideoToolbox-only variant
- Negative: non-launch error, unrelated framework (
libc++.dylib), symbol without launch wrapper, generic unrelated error - Platform: off-darwin returns undefined, on-darwin returns remediation with both env vars named
- Content: asserts
HYPERFRAMES_BROWSER_PATH,PRODUCER_HEADLESS_SHELL_PATH, andchrome-headless-shell@150are all present in the output
-
No
os.release()gate needed. The symbol_kVTCompressionPropertyKey_ReferenceBufferCountis itself the version discriminator — it only exists on macOS 13+, so a dyld failure to find it IS the version proof. The PR description explains this well; the comment in the source explains it well. No redundant check needed. -
Remediation path is x64-specific (cache path includes
chrome-headless-shell-mac-x64). Reasonable — the field signal is fromdarwin/x64, and arm64 Macs shipped with macOS 11 but have been on 13+ since launch year. An arm64 user on macOS <13 would be extremely rare.
Verdict
Clean, well-scoped fix. Detector is tight (three-gate AND), platform-gated, and well-tested. Slots into the existing remediation cascade without touching Linux or Windows paths.
— Miga
What
Add a darwin-scoped launch-crash remediation to
handleRenderErrorinpackages/cli/src/commands/render.ts. When the pinnedchrome-headless-shellbinary fails to launch withdyld: Symbol not found: _kVTCompressionPropertyKey_ReferenceBufferCountfromVideoToolbox, surface a scopederrorBoxthat namesHYPERFRAMES_BROWSER_PATH(and itsPRODUCER_HEADLESS_SHELL_PATHalias — see #2471) with a macOS-specificchrome-headless-shell@150install command.Why
Field feedback in #hyperframes-cli-feedback — message ts
1784227832,darwin/x64macOS 12, HyperFrames CLI0.7.60— hit the exact errordyld: Symbol not found: _kVTCompressionPropertyKey_ReferenceBufferCountreferenced fromVideoToolboxwhen launching the bundledchrome-headless-shell mac-152.0.7928.2. The symbol was added in macOS 13; older hosts cannot resolve it, so the binary aborts at load before any browser process starts.The reporter recovered by installing
chrome-headless-shell@150via@puppeteer/browsersand pointingPRODUCER_HEADLESS_SHELL_PATHat it. Theircheck/snapshotcommands accepted the older cached shell (they callensureBrowser()withoutpreferManagedChrome, so any cached headless-shell is fine), butrenderrequires the pinned v152 viapreferManagedChrome: trueand therefore couldn't fall back on its own. Their49s 1080x1920render then completed cleanly.The CLI's generic post-render hint (
Try --dockeretc.) doesn't name any of the browser-path env vars, so the workaround is undiscoverable unaided.This is the third platform-scoped sibling of #2443 (download-time hint):
STATUS_STACK_BUFFER_OVERRUNat launch (open)Same remediation surface, different trigger and platform.
How
packages/cli/src/browser/macosOldChromeCrash.ts— exportsisMacosOldChromeCrashError(errorMessage)andmacosOldChromeCrashRemediation(errorMessage). The detector requires ALL of:Failed to launch the browser processSymbol not found:signal_kVTCompressionPropertyKey_ReferenceBufferCountOR theVideoToolboxframework namemacosOldChromeCrashRemediationreturnsundefinedoff darwin.render.tshandleRenderErrorinvokesmacosOldChromeCrashRemediation(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; the Windows detector proposed in fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on Windows chrome-headless-shell launch crashes #2481 is untouched (different file).os.release()gate. The symbol-name signal is macOS-version-specific by construction:_kVTCompressionPropertyKey_ReferenceBufferCountonly exists on macOS 13+, so if a user's dyld cannot find it, their host is <13. ADarwin 22+check would be redundant.HYPERFRAMES_BROWSER_PATH(canonical, per fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on pinned browser download failures #2443/fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on Windows chrome-headless-shell launch crashes #2481) and itsPRODUCER_HEADLESS_SHELL_PATHalias (per fix(cli): honor PRODUCER_HEADLESS_SHELL_PATH in findFromEnv #2471) because the reporter reached for the alias first — matching the discovery path the docs already point at.Net diff: +182 / -0 across 3 files (2 new, 1 modified).
Test plan
packages/cli/src/browser/macosOldChromeCrash.test.ts(11 vitest cases):ReferenceBufferCountsymbol name (future pinned builds may hit sibling VT symbols)chromeLaunchRemediation)libc++.dylib) — needs different remediationbunx vitest run src/browser/macosOldChromeCrash.test.ts— 11/11 passing, 4msbunx oxlint packages/cli/src/browser/macosOldChromeCrash{,\.test}.ts packages/cli/src/commands/render.ts— 0 warnings 0 errorsbunx oxfmt— clean on all three filesPRODUCER_HEADLESS_SHELL_PATH) confirms the escape hatch works.Enterprise release / feature flag holdout
UX/Screenshot recording