Skip to content

fix(skills): caption and map scripts never fall back to the installed Chrome, and close it cleanly - #5918

Open
JimmySadek wants to merge 1 commit into
heygen-com:mainfrom
JimmySadek:fix/skills-headless-chrome
Open

JimmySadek wants to merge 1 commit into
heygen-com:mainfrom
JimmySadek:fix/skills-headless-chrome

Conversation

@JimmySadek

Copy link
Copy Markdown

What

The embedded-captions scripts and the motion-graphics map baker only launch a chrome-headless-shell, never the installed Google Chrome app, and close it without killing it.

Why

When hfBrowser() (from #5900) can't reach a CLI, it silently uses /Applications/Google Chrome.app. That happens when skills are installed with skills experimental_install into a project that has hyperframes only in node_modules/.bin: hfCli() throws, and hyperframes is not on PATH. With no cached shell, hyperframes browser path also prints the system Chrome.

On macOS, each run of the Chrome app that does not end with browser.close() leaves a com.google.Chrome.code_sign_clone folder of about 1.4 GB in /private/var/folders/.../X/. On our Macs, both SIGKILL and a plain SIGTERM of Chrome.app left one. Only browser.close() (CDP Browser.close), or a binary outside an app bundle such as chrome-headless-shell, is safe.

These scripts kill Chrome on two paths:

  1. process.exit() inside the try that owns the browser: measure-layout.cjs ("timeline never registered") and check-rail-climax.cjs ok() / fail(), including the render-blocking duplicate-word fail. Exiting skips finally, and Puppeteer's exit handler then SIGKILLs Chrome.
  2. The 8 s close race in measure-layout.cjs, check-rail-climax.cjs and preview-frames.cjs gives up and leaves Chrome running until the process exits.

bake-basemap.mjs has its own lookup that ignores HYPERFRAMES_BROWSER_PATH, sorts cached shells by name, and ends with /Applications/Google Chrome.app.

Related work

Refs #5900.

How

embedded-captions/scripts/hf-cli.cjs

  • hfBrowser() order:
    1. HYPERFRAMES_BROWSER_PATH, if executable. No subprocess.
    2. The pinned build that hyperframes browser path prints, asked through hfCli(), then each node_modules/.bin/hyperframes up from the working folder and the script folder (each binary once), then hyperframes on PATH (30 s timeout each).
    3. The newest cached chrome-headless-shell in ~/.cache/hyperframes/chrome, then ~/.cache/puppeteer, sorted by version.
    4. Otherwise throw: "No headless Chrome found. Run npx hyperframes browser ensure, or set HYPERFRAMES_BROWSER_PATH to a chrome-headless-shell binary."
  • Steps 2 and 3 accept only a chrome-headless-shell, so the system Chrome the CLI prints with an empty cache is skipped.
  • New closeBrowser(browser, ms = 5000): races browser.close() against 5 s, then SIGKILLs the browser process only if it is still alive.

measure-layout.cjs, check-rail-climax.cjs: throw instead of process.exit() inside the browser try; .catch() exits with the same codes (measure 4, rail-climax 0 / 2). finally calls closeBrowser().

preview-frames.cjs, check-overflow.cjs: finally, and check-overflow's inconclusive early return, call closeBrowser().

motion-graphics/categories/maps/bake-basemap.mjs: lookup is CHROME, HYPERFRAMES_BROWSER_PATH, the newest cached chrome-headless-shell (by version), then the Linux paths. The /Applications/Google Chrome.app fallback is removed. Close uses the same 5 s race, then SIGKILL.

skills-manifest.json: regenerated.

Behaviour change: a machine with no chrome-headless-shell and no HYPERFRAMES_BROWSER_PATH now gets an error with the install command instead of the installed Chrome. HYPERFRAMES_BROWSER_PATH set to the Chrome app still works. check-rail-climax.cjs keeps its rule that infrastructure never blocks: with no headless shell it skips (exit 0).

Test plan

On one macOS arm64 machine. Not run on Linux or Windows.

  • Unit tests: node --test hf-cli.test.mjs preview-frames.test.mjs render-and-composite.test.mjs → 10/10 pass, with HYPERFRAMES_BROWSER_PATH unset.
  • Resolver cases with a stub CLI and fake caches:
    • the env var wins;
    • the CLI's pinned shell beats an older cached one;
    • the CLI printing Chrome.app falls to the cache, which picks 150 over 99 by version;
    • with Chrome.app and empty caches, it throws;
    • with no CLI it uses the cache;
    • with nothing at all, it throws.
  • Manual: all four caption scripts on a scratch composition, with no env var. They used the shell the CLI chose, and exit codes were unchanged: measure 0, broken timeline 4, overflow 0, rail-climax 0, duplicate word 2, preview 0. 0 browsers left, and the code_sign_clone count did not change.
  • Manual: a 2-frame bake-basemap.mjs run with no env var: exit 0, MP4 written, cached headless shell, 0 left.
  • oxfmt --check, oxlint, scripts/lint-skills.ts, gen-skills-manifest.ts (in sync), check-comment-citations.mjs (one warning on an existing comment, not this diff), comment-ratchet.mjs.
  • Documentation updated: not needed.
  • Comments follow CONTRIBUTING.md "Comments".

🤖 Generated with Claude Code

… Chrome, and close it cleanly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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