{"record":{"id":"28403e5ef4b11eff","repo":"heygen-com/hyperframes","slug":"ghost-composite-returned-no-data","errorCode":null,"errorMessage":"ghost composite returned no data","messagePattern":"ghost composite returned no data","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/motionShot.ts","lineNumber":544,"sourceCode":"    throw new Error(\n      \"--ghost renders a canvas/WebGL motion trail, but this composition has no <canvas>. Use the default --shot onion for DOM/SVG transform motion.\",\n    );\n  }\n  const frames: string[] = [];\n  for (const t of times) {\n    frames.push(await captureGhostFrame(page, t));\n  }\n  const label = `${cameraLabel(camera)}  ·  rendered onion  ·  ${times.length} frames  ·  t ${times[0]}–${times[times.length - 1]}s`;\n  const dataUrl = (await page.evaluate(\n    compositeGhostFrames,\n    frames,\n    ghostAlphas(frames.length),\n    size.width,\n    size.height,\n    label,\n  )) as string;\n  const b64 = String(dataUrl).replace(/^data:image\\/png;base64,/, \"\");\n  if (!b64) throw new Error(\"ghost composite returned no data\");\n  writeFileSync(outPath, Buffer.from(b64, \"base64\"));\n  return outPath;\n}\n\n// Default (marker) onion-skin: seek to each sample time, read every element's\n// projected corners. Marker children (zero-size) inherit the element's full\n// transform chain, so their screen positions ARE the 3D projection of each\n// corner — this is how 3D comes \"for free\" without an #stage assumption.\nasync function captureMarkerOnionSkin(\n  page: import(\"puppeteer-core\").Page,\n  requests: ShotRequest[],\n  times: number[],\n  size: FrameSize,\n  camera: OrbitCamera,\n  frame: { layout: \"path\" | \"strip\"; fit: boolean; hasWindow: boolean },\n  outPath: string,\n): Promise<string> {\n  // Orbit camera as its own step (keeps the sampler simple), only when angled.","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/motionShot.ts#L526-L562","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before — cross-origin image taints the canvas\nconst tex = new Image(); tex.src = 'https://cdn.example.com/tex.png';\n// after — set crossOrigin before src, or use a local asset\ntex.crossOrigin = 'anonymous';\ntex.src = 'assets/tex.png';","handlingStrategy":"try-catch","validationCode":"// Ensure all images drawn to canvas are same-origin or CORS-enabled:\nconst img = new Image();\nimg.crossOrigin = 'anonymous'; // set BEFORE src\nimg.src = 'assets/tex.png';   // local asset avoids taint entirely","typeGuard":null,"tryCatchPattern":"try {\n  await captureGhostOnionSkin(page, requests, times, size, camera, outPath);\n} catch (e) {\n  if (e instanceof Error && /ghost composite returned no data/.test(e.message)) {\n    // check for cross-origin/tainted canvas, then retry or fall back to marker onion\n  } else throw e;\n}","preventionTips":["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"],"tags":["motion-shot","ghost","canvas-taint","cors","rendering"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}