heygen-com/hyperframes · error · Error
ghost composite returned no data
Error message
ghost composite returned no data
What it means
Thrown by captureGhostOnionSkin when the in-browser compositeGhostFrames function returned an empty string. This happens when the 2D context could not be acquired (getContext('2d') returned null), every frame's image failed to load, or toDataURL produced an empty result — often due to a cross-origin/tainted canvas that blocks pixel readback.
Source
Thrown at packages/cli/src/commands/motionShot.ts:544
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");
writeFileSync(outPath, Buffer.from(b64, "base64"));
return outPath;
}
// Default (marker) onion-skin: seek to each sample time, read every element's
// projected corners. Marker children (zero-size) inherit the element's full
// transform chain, so their screen positions ARE the 3D projection of each
// corner — this is how 3D comes "for free" without an #stage assumption.
async function captureMarkerOnionSkin(
page: import("puppeteer-core").Page,
requests: ShotRequest[],
times: number[],
size: FrameSize,
camera: OrbitCamera,
frame: { layout: "path" | "strip"; fit: boolean; hasWindow: boolean },
outPath: string,
): Promise<string> {
// Orbit camera as its own step (keeps the sampler simple), only when angled.View on GitHub (pinned to c2996c8626)
Solutions
- Ensure all images/textures drawn to canvas use CORS-enabled URLs (crossOrigin='anonymous') and serve proper headers
- Freeze remote assets locally with media-use so they are same-origin
- Retry the shot; transient GPU/context loss in headless Chrome can produce this
- Reduce the number of ghost samples if memory pressure is suspected
Example fix
// before — cross-origin image taints the canvas const tex = new Image(); tex.src = 'https://cdn.example.com/tex.png'; // after — set crossOrigin before src, or use a local asset tex.crossOrigin = 'anonymous'; tex.src = 'assets/tex.png';
Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure all images drawn to canvas are same-origin or CORS-enabled: const img = new Image(); img.crossOrigin = 'anonymous'; // set BEFORE src img.src = 'assets/tex.png'; // local asset avoids taint entirely
Try / catch
try {
await captureGhostOnionSkin(page, requests, times, size, camera, outPath);
} catch (e) {
if (e instanceof Error && /ghost composite returned no data/.test(e.message)) {
// check for cross-origin/tainted canvas, then retry or fall back to marker onion
} else throw e;
} Prevention
- Use local (frozen) assets for any image drawn to canvas
- Set crossOrigin='anonymous' before the src attribute on remote images
- Retry once on transient GPU/context loss in headless Chrome
When it happens
Trigger: A canvas drawing cross-origin images without CORS headers, tainting the canvas so toDataURL throws or returns empty. Also when the composite canvas context is unavailable (headless GPU context lost).
Common situations: Compositions loading remote textures/images into WebGL without crossOrigin attributes. Headless Chrome GPU crashes that lose the 2D context. Very large frame sizes where allocation fails.
Related errors
- --ghost renders a canvas/WebGL motion trail, but this compos
- screenshot returned no data
- hf-seek waitUntil() must be called synchronously
- --shot: --selector '${scopeSelector}' matched no element.
- --shot: nothing animates under '${scopeSelector}'. Nearest a
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/28403e5ef4b11eff.
Report an issue: GitHub.