Skip to content

fix(cli): stop chrome-headless-shell download hangs from stalling forever - #3883

Open
miga-heygen wants to merge 1 commit into
mainfrom
fix/chrome-headless-download-stall-proxy-timeout
Open

fix(cli): stop chrome-headless-shell download hangs from stalling forever#3883
miga-heygen wants to merge 1 commit into
mainfrom
fix/chrome-headless-download-stall-proxy-timeout

Conversation

@miga-heygen

Copy link
Copy Markdown
Contributor

Summary

PRINFRA-682: hyperframes render/browser ensure hangs at "Downloading Chrome... 0%" forever on a network that requires a proxy, with no timeout and no fallback signal.

Investigated (against current origin/main, not the older npm-tarball source the original vault finding traced) and confirmed both parts of the root cause independently:

  1. No timeout anywhere in the download chain. @puppeteer/browsers' install()downloadFile()httpRequest() (lib/httpUtil.js in the resolved 3.2.1) opens a bare node:http(s) request with no AbortController/timeout at all — confirmed by reading the actual resolved package source in a fresh worktree install.
  2. Proxy support is dead-on-arrival. That same httpRequest() does try await import('proxy-agent'), but proxy-agent is only an optional peer dependency of @puppeteer/browsers that hyperframes never installs — the import throws, is caught silently, and the request falls through to a bare Node agent that never reads HTTP_PROXY/HTTPS_PROXY. On a proxy-only network, that direct-connection attempt doesn't fail fast; it hangs.

What I did NOT ship, and why: the referenced vault finding also suggested dropping preferManagedChrome: true from render's ensureBrowser() call (or falling back to findSystemBrowser()) so render could use an already-working system Chrome instead of downloading. Source investigation shows this is a deliberate design constraint, not an oversight — manager.ts's EnsureBrowserOptions.preferManagedChrome doc comment and macosOldChromeCrash.ts both document that render needs the exact pinned Chrome build for canvas.drawElementImage support (Stable-channel system Chrome doesn't have it and used to crash drawElement-eligible renders outright, HF#2060), and that the established remediation for a broken managed-download path is surfacing HYPERFRAMES_BROWSER_PATH (already implemented via wrapDownloadFailureWithBrowserPathHint), not silently falling back to system Chrome. Reporting this back to cli-feedback rather than implementing it — it would reintroduce a known crash class.

Fix implemented (the timeout/proxy half only):

  • packages/cli/src/browser/manager.ts: new exported withDownloadStallGuard() — a watchdog that races install() against a "no progress" timer. It resets on every progress tick (and once before the first one), so it's a stall guard, not a total-duration cap — a slow-but-actually-progressing download is never affected, only a connection that stops producing bytes entirely.
  • Wired into downloadBrowser()'s runInstall, so a stalled attempt surfaces through the existing wrapDownloadFailureWithBrowserPathHint error-rewrap path (same one that already handles "all CDN providers failed") instead of hanging.
  • Added proxyDownloadStallHint(): when HTTP_PROXY/HTTPS_PROXY is actually set in the environment, appends a short clause explaining why it likely stalled (the proxy isn't honored) on top of the existing HYPERFRAMES_BROWSER_PATH remediation — turning "download stalled, no idea why" into an actionable diagnosis without adding a new dependency (proxy-agent pulls in several sub-agents and is deliberately optional upstream; this mirrors the dependency-free NODE_USE_ENV_PROXY hint pattern already used in publishProject.ts's proxySupportHint()).
  • Does not touch @puppeteer/browsers itself, does not add proxy-agent as a dependency, does not change render's browser-selection policy.

Test plan

  • New withDownloadStallGuard unit tests (6): resolves normally before any stall, rejects when no progress ever arrives, resets on every progress tick so a slow-but-live download is unaffected, still trips on a mid-download stall, propagates a genuine rejection unchanged (not mislabeled as a stall), forwards progress ticks to the caller.
  • New downloadBrowser/ensureBrowser wiring tests (3): proxy hint appended when HTTPS_PROXY is set, omitted when it isn't, and a full end-to-end case (via fake timers) proving a download that never responds surfaces "Download stalled" + the HYPERFRAMES_BROWSER_PATH hint instead of hanging.
  • All 51 tests in manager.test.ts pass (42 pre-existing + 9 new), no regressions.
  • bunx tsc --noEmit -p packages/cli clean.
  • bunx oxlint/oxfmt --write clean.
  • bunx fallow audit --base origin/main --fail-on-issues clean (had to reword two JSDoc comments that literally spelled import('proxy-agent') as prose — fallow's dependency scanner flagged that string as a real unlisted import).
  • packages/cli/src/commands/render test suite (95 tests) — no regressions.
  • Pre-commit hooks (lefthook: largefiles/tracked-artifacts/lint/format/fallow/typecheck/commitlint) all passed on the actual commit.

🤖 Generated with Claude Code

…ever

@puppeteer/browsers' download path has no AbortController/timeout anywhere
in its chain, and its proxy support is inert (proxy-agent is only an
optional peer dependency hyperframes doesn't install) — so on a network
that requires a proxy, the download attempts a direct connection and hangs
at "Downloading Chrome... 0%" forever instead of failing.

Add a stall watchdog (resets on every progress tick, not a total-duration
cap, so a slow-but-live download is unaffected) around the install() call,
and name HTTP(S)_PROXY as the likely cause in the existing
HYPERFRAMES_BROWSER_PATH remediation hint when one is configured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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