render: a graded <video> inside a shader transition stalls frame capture (0.8.40 and 0.8.41)
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
- Follow the gist's Setup block — it installs
[email protected],@hyperframes/[email protected]and[email protected], copies the two browser bundles next toindex.html, and makes a 4 sclip.mp4withffmpeg -f lavfi -i "testsrc2=s=540x960:d=4:r=30". - Render it as published — the
<video>carriesdata-color-grading='{"preset":"warm-daylight","intensity":1}':npx hyperframes render . --fps 30 --workers 2 --output graded.mp4 - Delete that one attribute from the
<video>and render again: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 s — 871.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 654and 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
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.
Source: heygen-com/hyperframes