Source: Common Mistakes, Troubleshooting — HeyGen HyperFrames docs
The debugging reference for HeyGen Hyperframes — the two docs pages an agent reaches for when a composition renders wrong. Common Mistakes covers the 8 authoring pitfalls the linter mostly cannot catch (each as symptom → cause → fix); Troubleshooting covers the environment/tooling/render problems it can. For the framework overview, primitives, and skill install, start at the hub; this page is the self-correction playbook.
Key Takeaways
- Two authoring mistakes cause most broken compositions: animating
<video>dimensions directly, and controlling media playback from scripts. If the video looks wrong, check those two first. - HyperFrames owns media playback. Never call
video.play()/video.pause()/ setaudio.currentTime— drive timing throughdata-start,data-media-start,data-volumeand animate only visual props with GSAP. - Composition length = GSAP timeline length, not video length. A short timeline truncates a long video. Extend with
tl.set({}, {}, SECONDS); verify withnpx hyperframes compositions. - The timeline key must equal
data-composition-idexactly or no animation runs — the single most common “static composition” cause. - Two perf killers are visual, not structural: oversized source images (Chrome decodes to
w×h×4RGBA regardless of file size) and stackedbackdrop-filter: blur()layers. Both stutter preview before they slow render. npx hyperframes lintis the cheap pre-render gate for structural errors (missingdata-composition-id, missingclass="clip", overlapping clips, unmuted video, deprecated attrs);npx hyperframes doctoris the environment gate (Node, FFmpeg, Docker).- Preview ≠ render. Preview can stutter while the rendered mp4 is perfect (render captures frame-by-frame); use
--dockerfor deterministic output across machines.
Common Mistakes
Authoring pitfalls that break compositions. The page is explicit that these are mostly issues the linter cannot catch — except #4, which it flags. Each entry: what breaks → why → the fix.
1. Animating video element dimensions
- Breaks: video frames stop updating, or browser performance collapses.
- Why: GSAP animating
width,height,top, orleftdirectly on a<video>element can make the browser stop rendering frames. - Fix: wrap the
<video>in a<div>and animate the wrapper, never the video element itself.
2. Controlling media playback in scripts
- Breaks: audio/video plays out of sync, or plays when it shouldn’t.
- Why: calling
video.play(),video.pause(), or settingaudio.currentTimefights the framework — HyperFrames owns all media playback and readsdata-start,data-media-start, anddata-volumeto schedule it. - Fix: don’t touch playback at all; use GSAP for visual animation only.
// Broken — conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
// Fixed — let the framework drive media; animate visuals only
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);3. Composition duration shorter than the video (cuts off early)
- Breaks: video plays a few seconds then stops; timeline shows 8-10s even though the source is minutes long.
- Why: composition duration equals the GSAP timeline duration, not the video’s
data-duration. If your last tween ends at 8s, the composition is 8s. - Fix: extend the timeline with a zero-duration tween at the target end time; confirm with
npx hyperframes compositions.
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
// Extend the timeline to 283s to match the video length
tl.set({}, {}, 283);4. Missing class="clip" on timed elements
- Breaks: elements stay visible the whole time, ignoring
data-start/data-duration. - Why:
class="clip"is what tells the runtime to manage an element’s visibility lifecycle; without it the element is always rendered. - Fix: add
class="clip"to every timed element. This is the one mistake the linter catches —npx hyperframes lintflags timed elements missing it.
5. Oversized source images
- Breaks: preview stutters on image-heavy scenes; render slows down.
- Why: Chrome decodes images to raw RGBA bitmaps sized
width × height × 4bytes — independent of file size on disk. A 7000×5000 JPEG is ~140MB decoded even at 2MB on disk, and the compositor resamples that huge texture every frame. - Fix: downscale sources to at most ~2× the canvas (3840×2160 is plenty for a 1080p comp).
mkdir -p assets/resized
mogrify -path assets/resized -resize 3840x3840\> assets/*.jpg6. Heavy backdrop-filter blur stacks
- Breaks: specific scenes drop to 5-10fps while the rest is fine.
- Why: each
backdrop-filter: blur()layer makes the compositor sample behind the element, run a blur kernel, and re-composite — every frame. Stacked layers multiply the cost (8 layers/side = 16 passes/frame). - Fix: keep stacks to 2-3 tuned radii per region, avoid radii above 64px on large areas, or pre-render a static blur to a PNG and overlay it as a normal
<img>.
/* Expensive: 8 stacked layers */ /* Cheaper: 3 hand-picked radii */
.pb-1 { backdrop-filter: blur(1px); } .pb-1 { backdrop-filter: blur(4px); }
/* ...up to blur(128px) */ .pb-2 { backdrop-filter: blur(16px); }
.pb-3 { backdrop-filter: blur(48px); }7. Expected HDR output but got SDR
- Breaks: render looks like SDR;
ffprobereportscolor_transfer=bt709. - Why: by default HyperFrames only switches to HDR encoding when a source
<video>or<img>carries BT.2020 / PQ / HLG color metadata. - Fix: check sources with
ffprobe -v error -show_streams source.mp4 | grep color_transfer; render with--format mp4(HDR needs MP4, not MOV/WebM); force it with--hdr; and drop any--sdrflag when HDR sources are present.
8. Timeline key doesn’t match data-composition-id
- Breaks: animations don’t play; the composition appears static.
- Why: the key registered on
window.__timelines[...]must match the root element’sdata-composition-idexactly. A mismatch means the runtime never finds the timeline. - Fix: register under the same id used in the HTML.
// HTML: <div data-composition-id="my-video">
window.__timelines["root"] = tl; // broken — key mismatch
window.__timelines["my-video"] = tl; // fixed — matches data-composition-idDebugging checklist (page’s recommended order): (1) run npx hyperframes lint; (2) confirm the timeline is registered and the key matches data-composition-id; (3) confirm GSAP animates only visual props (opacity, transform, color); (4) confirm the timeline is long enough (tl.set({}, {}, DURATION)); (5) open the browser console — runtime errors surface as [Browser:ERROR]; (6) fall through to Troubleshooting for environment/render issues.
Troubleshooting
Environment, tooling, and rendering issues (symptom → cause → solution).
- “No composition found” → the directory has no valid composition / the root element lacks
data-composition-id. Runnpx hyperframes initfrom an example, or give the rootdata-composition-id(plusdata-start/data-width/data-height). - “FFmpeg not found” → local rendering needs FFmpeg. Install (
brew install ffmpeg,sudo apt install ffmpeg, or the Windows download + PATH), verify withffmpeg -version, thennpx hyperframes doctor. Can’t install it? Render in Docker (npx hyperframes render --docker) — the image bundles FFmpeg. - Lint errors →
npx hyperframes lintreports the structural set: missingdata-composition-id, missingclass="clip", overlapping timelines on the samedata-track-index, unmuted video (mutedrequired unlessdata-has-audio="true"), and deprecated attribute names (data-layer/data-endare replaced — see HyperFrames HTML Schema). - Preview not updating → you’re editing the wrong
index.html, or the cache is stale. Check the preview-server terminal, restartnpx hyperframes preview, hard-refresh (Ctrl/Cmd+Shift+R), clear the browser cache for unreflected CSS. - Preview stutters / low frame rate (but the mp4 is fine) → individual frames exceed the 16-33ms paint budget; render hides this because it captures one frame at a time. Usual culprits, most-to-least: stacked
backdrop-filter: blur()(radii > 32px), >4K images in small regions,filter: blur()/drop-shadow()on large elements, many animatedbox-shadow/text-shadow. Diagnose in Chrome DevTools → Performance (look for long “Composite Layers” / “Paint” tasks). Workaround:npx hyperframes render --quality draftand watch the output. See HyperFrames Rendering. - Render looks different from preview → font availability, Chrome version, or system compositing differs across machines. Use
--dockerfor deterministic output. - Docker mode fails to start → check
docker info. Common causes: daemon not running; permission denied (add your user to thedockergroup and restart the shell); image pull fails (first render downloads the HyperFrames image — check connectivity). - Render is slow →
--quality draftduring dev;npx hyperframes benchmarkto find the optimal worker count; compare--no-browser-gpu;--gpufor hardware-accelerated encoding (local only); drop to--fps 24if 30 isn’t needed; prune unnecessary elements / overly complex animation. - Still stuck →
npx hyperframes infofor system + project details, check the GitHub issues, and open a new one with that output plus repro steps.
Prevention
Two non-interactive CLI gates catch most failures before a render, which makes them ideal to wire into an agent’s pre-render step (see HyperFrames Packages & CLI):
npx hyperframes lint— structural validation. Catches missingdata-composition-id, missingclass="clip"on timed elements, overlapping clips on adata-track-index, unmuted video elements, and deprecated attribute names. It does not catch the runtime/visual pitfalls above (media-playback control, timeline-length truncation, video-dimension animation, perf blur/image cost) — those need the debugging checklist.npx hyperframes doctor— environment validation. Checks Node.js version, FFmpeg availability, Docker status, and other requirements. Resolvedoctorfindings before rendering.npx hyperframes compositions— sanity-check the resolved duration of each composition; a shorter-than-expected number means the timeline needs extending (mistake #3).
Agent rule of thumb: doctor once per environment, then lint + compositions before every render; treat a clean lint as necessary-but-not-sufficient because the highest-frequency mistakes are the ones lint can’t see.
Try It
- Before any render in an agent loop, run
npx hyperframes lint && npx hyperframes compositionsand only proceed on a clean lint with the expected duration. - Reproduce the #1 mistake to learn the failure signature: animate
width/heighton a raw<video>, watch frames freeze, then wrap it in a<div>and animate the wrapper. - If a composition renders static, jump straight to the timeline key — grep your script for
window.__timelines[and confirm the key equals the root’sdata-composition-id. - For HDR work, gate the pipeline on
ffprobe ... | grep color_transferand pass--format mp4 --hdr. - Add
npx hyperframes doctorto project onboarding so FFmpeg/Docker gaps surface before the first render, not during it.