Skip to content

hyperframes render dies with Page.captureScreenshot protocolTimeout when the host is swap-thrashed; cap --workers instead of raising the timeout

Symptom: npx hyperframes render (0.7.107, macOS arm64) runs for ~19 minutes and then hard-fails with

[Parallel] Capture failed: Worker 0: Page.captureScreenshot timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.

No output file. The message names puppeteer's protocolTimeout (engine default 300000ms, PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS), which is the symptom, not the cause: one capture worker was starved long enough that a single CDP screenshot never answered in 5 minutes.

What it actually was, on a 10-core / 24 GB M5 rendering a 47s 1080x1920@60 (2823 frames) composition with a 180 MB local <video> plus 8 backdrop-filter: blur(16px) cards:

  1. Swap was exhausted (9.2 GB of 10 GB used, 7.1M pageouts) before the render started.
  2. Auto worker sizing is cores / coresPerWorker (2.5 default) = 4 Chrome instances, each decoding the same 180 MB video. Sizing reads cores and total RAM, never free memory or swap pressure, so a thrashing host gets the same 4 workers as an idle one.
  3. An orphaned headless Chrome from an earlier render (ppid 1, --headless=new, days old, ~370 MB across 4 renderer helpers) was still resident. Renderer helpers survive pkill of the parent; kill them by PID.

Fix that worked, same composition, same machine, no composition change: kill the orphans, then

PRODUCER_BROWSER_GPU_MODE=hardware PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS=900000 \
  npx hyperframes render public -o output.mp4 --workers 3

18m48s failure -> 2m19s success (2823/2823 frames, artifact validated). The dominant term was concurrency, not the timeout; raising protocolTimeout alone would have bought a longer stall.

Diagnostic order for any 'render was slow then timed out' on macOS:

  • sysctl vm.swapusage first. Near-full swap explains a stalled worker better than any composition property.
  • ps -Ao pid,ppid,command | grep 'headless=new' for orphaned render browsers; ppid=1 means a previous run leaked them.
  • Then cap --workers (or PRODUCER_MAX_WORKERS) to 2-3 rather than reaching for timeouts.
  • Keep PRODUCER_BROWSER_GPU_MODE=hardware on macOS; software rasterization is the other well-known way to time this out.

Cheap pre-flight that also validates the edit: npx hyperframes snapshot <dir> --at t1,t2,t3 renders single frames in ~10s and warms the media proxies, so a broken composition or a wedged browser surfaces before you spend 20 minutes on a capture run.

No signals yet