render: a graded <video> inside a shader transition stalls frame capture (0.8.40 and 0.8.41)

Author: srinvasmishraCreated Sep 16, 2026Updated Sep 16, 2026

Describe the bug

A <video> carrying data-color-grading inside a composition that uses @hyperframes/shader-transitions makes hyperframes render stop making progress during frame capture. Removing only the data-color-grading attribute — one attribute, nothing else on the page changes — renders the same composition in 29.6 s.

A graded <img> in the same composition composites fine, so this is specific to <video>.

0.8.41's new transient-Page.captureScreenshot retry does not cover it: the worker is torn down at the 300 s protocol timeout and the retry stalls at the same frame.

Link to reproduction

https://gist.github.com/srinvasmishra/a019429ec88d76c7d013b70518bb2841

Two files: index.html (the composition) and README.md (the exact npm i / ffmpeg / curl setup, since the repro needs a test clip and the two browser bundles).

Steps to reproduce

  1. Follow the gist's Setup block — it installs [email protected], @hyperframes/[email protected] and [email protected], copies the two browser bundles next to index.html, and makes a 4 s clip.mp4 with ffmpeg -f lavfi -i "testsrc2=s=540x960:d=4:r=30".
  2. Render it as published — the <video> carries data-color-grading='{"preset":"warm-daylight","intensity":1}':
    bash
    npx hyperframes render . --fps 30 --workers 2 --output graded.mp4
  3. Delete that one attribute from the <video> and render again:
    bash
    npx hyperframes render . --fps 30 --workers 2 --output ungraded.mp4

The composition is otherwise minimal: two scene divs, one whip-pan transition at t=1.75 s via HyperShader.init({ ..., timeline: tl }), a paused GSAP timeline registered as window.__timelines["main"]. hyperframes lint is clean.

Expected behavior

Both render. data-color-grading on a <video> should composite the way it already does on an <img> in the same composition.

Actual behavior

frames result
ungraded control 1200 / 1200 29.6 s871.6 KB · 40.0s video
graded stops at 653 / 1200 ❌ never finishes

It is not a hard hang — it degrades to about one frame every four minutes. From the graded run's own [Render:trace] heartbeats (phase: capture_disk, captureOperation: captureScreenshot throughout):

elapsedMs   70228  framesCompleted 653
elapsedMs  130229  framesCompleted 653
elapsedMs  250229  framesCompleted 653
elapsedMs  370230  framesCompleted 653
elapsedMs  490230  framesCompleted 653
elapsedMs  850231  framesCompleted 653
elapsedMs 1090232  framesCompleted 654

and it ends, after 20.6 minutes, at:

failedStage: "Capturing frame 630/1200 (2 workers)"
error: "[Parallel] Capture failed: Worker 0: Runtime.callFunctionOn timed out."
observedFailedPhase: "capture_disk"
isTimeout: false
browserDiagnostics: { total: 3, errors: 0, pageErrors: 0, requestFailed: 0,
                      httpErrors: 0, navigationFailures: 0, consoleErrors: 0, consoleWarnings: 0 }

The page itself is healthy — zero page errors, zero console errors, zero failed requests. The capture just stops advancing. It reproduces at exactly frame 653 on every run, on this machine.

An earlier run of the same repro hit the 300 s protocol timeout, tore down worker 0 and started capture-attempt-1/retry-1-batch-0-worker-0, which stalled identically — so the 0.8.41 retry path is reached and does not help.

Raising --protocol-timeout only moves the deadline; at ~1 frame per 4 minutes the remaining 546 frames would take about 36 hours.

Environment

bash
hyperframes doctor

  ✓ Version          0.8.41 (latest)
  ✓ Node.js          v22.22.1 (linux x64)
  ✓ CPU              32 cores · Intel(R) Core(TM) i9-14900HX @ 800MHz
  ✓ Memory           31.1 GB total · 17.0 GB available
  ✓ Disk             474.6 GB free
  ✓ Frames cache     /tmp/hyperframes-extract-cache-1000 · 474.6 GB free · default
  ✓ Archive extractor unzip
  ✓ /dev/shm         15904 MB
  ✓ Environment      non-TTY
  ✓ FFmpeg           ffmpeg 6.1.1-3ubuntu5 at /usr/bin/ffmpeg
  ✓ FFprobe          ffprobe 6.1.1-3ubuntu5 at /usr/bin/ffprobe
  ✓ Chrome           chrome-headless-shell 152.0.7977.30 (hardware GPU, ANGLE / Mesa Intel RPL-S)
  ✗ Docker           Not found

@hyperframes/[email protected], [email protected]. Also reproduces on 0.8.40, where we first hit it.

Notes / workaround

The host is not RAM-pressured (17 GB available) and the composition is one video and no images, so the Field signal hint the CLI prints about "heavy-asset compositions (9+ videos + 20+ images)" does not apply here.

Our workaround is to bake the grade into the clip before it reaches the page: port applyPrimaryGrade out of hyperframe-runtime.js, sample it into a 33³ .cube, apply it with ffmpeg lut3d, and flag the clip so the runtime does not grade it again. It works, but it is a fork of your colour maths pinned to a SHA of your shader, and we would like to delete it. Happy to test a fix.