heygen-com/hyperframes · warning · Error
--ghost renders a canvas/WebGL motion trail, but this compos
Error message
--ghost renders a canvas/WebGL motion trail, but this composition has no <canvas>. Use the default --shot onion for DOM/SVG transform motion.
What it means
Thrown by captureGhostOnionSkin when --ghost is requested but the composition contains no <canvas> element. The ghost mode composites real painted canvas/WebGL pixels across sample times to produce a rendered motion trail; without a canvas there are no pixels to capture. DOM/SVG transform motion is already covered by the default marker onion-skin and does not need --ghost.
Source
Thrown at packages/cli/src/commands/motionShot.ts:526
// Rendered ("ghost") onion-skin: screenshot the REAL painted stage at each
// sample and composite them as translucent ghosts. This is the onion-skin for
// canvas/WebGL motion the marker sampler is blind to (the markers project a
// bbox; the pixels are the motion). Works for any visual composition, but
// requires a <canvas> (DOM/SVG transform motion already shows up in the
// default marker onion).
async function captureGhostOnionSkin(
page: import("puppeteer-core").Page,
requests: ShotRequest[],
times: number[],
size: FrameSize,
camera: OrbitCamera,
outPath: string,
): Promise<string> {
await applyOrbitCameraIfAngled(page, requests, camera);
const hasCanvas = await page.evaluate(() => document.querySelectorAll("canvas").length > 0);
if (!hasCanvas) {
throw new Error(
"--ghost renders a canvas/WebGL motion trail, but this composition has no <canvas>. Use the default --shot onion for DOM/SVG transform motion.",
);
}
const frames: string[] = [];
for (const t of times) {
frames.push(await captureGhostFrame(page, t));
}
const label = `${cameraLabel(camera)} · rendered onion · ${times.length} frames · t ${times[0]}–${times[times.length - 1]}s`;
const dataUrl = (await page.evaluate(
compositeGhostFrames,
frames,
ghostAlphas(frames.length),
size.width,
size.height,
label,
)) as string;
const b64 = String(dataUrl).replace(/^data:image\/png;base64,/, "");
if (!b64) throw new Error("ghost composite returned no data");View on GitHub (pinned to c2996c8626)
Solutions
- Drop --ghost to use the default marker onion-skin, which captures DOM/SVG transform motion
- Only use --ghost when the composition has a <canvas> (WebGL/2D canvas rendering)
- Verify document.querySelectorAll('canvas').length > 0 in the rendered page before using --ghost
Example fix
// before — DOM/SVG composition, no canvas hyperframes shot --ghost out.png // after — default marker onion for DOM/SVG motion hyperframes shot out.png
Defensive patterns
Strategy: validation
Validate before calling
import { parseHTML } from 'linkedom';
function compositionHasCanvas(html: string): boolean {
return parseHTML(html).document.querySelectorAll('canvas').length > 0;
}
// before running --ghost
if (!compositionHasCanvas(html)) {
throw new Error('No <canvas> — use the default marker onion instead of --ghost');
} Prevention
- Only use --ghost for canvas/WebGL compositions
- Default to the marker onion (no --ghost) for DOM/SVG transform motion
When it happens
Trigger: Running `hyperframes shot --ghost out.png` on a composition that animates only DOM elements (divs, SVG) with CSS/GSAP transforms and contains no <canvas>.
Common situations: Agents that default to --ghost for all shots. Users who assume --ghost improves any motion capture. Compositions that use WebGL conditionally (the canvas is added by JS that hasn't run).
Related errors
- --shot: --selector '${scopeSelector}' matched no element.
- invalid-vars
- ${source}: ${errorMessage(error)}
- --batch must be a JSON array of objects, or an object with a
- ${source} contains zero rows.
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/cf8c82a2182fe8f5.
Report an issue: GitHub.