heygen-com/hyperframes · warning · Error

--shot: nothing animates under '${scopeSelector}'. Nearest a

Error message

--shot: nothing animates under '${scopeSelector}'. Nearest animated elements: ${nearest || "(none)"}.

What it means

Thrown by resolveScopedRequests when the --selector scope matched a real element (scopeExists === true) but no animated selectors were found underneath it — neither the scope element itself nor any descendant is in the animated-requests list. The message lists the nearest animated elements (up to 5) as diagnostic context so you can re-scope correctly.

Source

Thrown at packages/cli/src/commands/motionShot.ts:452

async function resolveScopedRequests(
  page: import("puppeteer-core").Page,
  requests: ShotRequest[],
  scopeSelector: string,
): Promise<ShotRequest[]> {
  const resolved = await resolveScopeInBrowser(
    page,
    scopeSelector,
    requests.map((r) => r.selector),
  );
  if (!resolved.scopeExists) {
    throw new Error(`--shot: --selector '${scopeSelector}' matched no element.`);
  }
  if (resolved.selectors.length === 0) {
    const nearest = requests
      .slice(0, 5)
      .map((r) => r.selector)
      .join(", ");
    throw new Error(
      `--shot: nothing animates under '${scopeSelector}'. Nearest animated elements: ${nearest || "(none)"}.`,
    );
  }
  return resolved.selectors.map((selector) => ({ selector }));
}

// In-tick capture: seek the timeline (fires the composition's onUpdate render
// synchronously via the shared window.__hfSeekAllAdapters) + nudge the
// three-adapter, then drawImage every <canvas> onto an offscreen canvas in the
// SAME tick — before the browser clears the GL drawing buffer (works without
// preserveDrawingBuffer; page.screenshot can't see the GL buffer here).
function captureGhostFrame(page: import("puppeteer-core").Page, t: number): Promise<string> {
  return page.evaluate(async (tt: number) => {
    await (
      window as unknown as { __hfSeekAllAdapters?: (time: number) => Promise<void> }
    ).__hfSeekAllAdapters?.(tt);
    const root = (document.querySelector("[data-composition-id]") ?? document.body) as HTMLElement;
    const rb = root.getBoundingClientRect();

View on GitHub (pinned to c2996c8626)

Solutions

  1. Re-scope to one of the nearest animated elements listed in the error message
  2. Drop --selector to let the shot sample all detected animated elements
  3. Verify the element you expect to animate actually has a registered timeline (window.__timelines)
  4. Ensure GSAP timelines are registered on window.__timelines so they are detected as animated

Example fix

// before — static wrapper has no animated children
hyperframes shot --selector '.static-clip' out.png
// after — scope the element that actually animates
hyperframes shot --selector '.animated-child' out.png
Defensive patterns

Strategy: validation

Validate before calling

// After bundling, check the scope has animated descendants:
const animated = await page.evaluate((scope: string) => {
  const root = document.querySelector(scope);
  if (!root) return { exists: false, animated: 0 };
  const timelines = Object.keys((window as any).__timelines ?? {});
  const animated = timelines.filter(sel => {
    const el = document.querySelector(sel);
    return el && (el === root || root.contains(el));
  }).length;
  return { exists: true, animated };
}, scopeSelector);
if (animated.animated === 0) {
  // pick a different scope or drop --selector
}

Prevention

When it happens

Trigger: Scoping to a static wrapper element (e.g. a .clip container) that has no animated children, or whose animated children are outside the request set. The scope exists but carries no motion sampled by the shot.

Common situations: Targeting a layout container instead of the animated child. Compositions where animation is on a sibling rather than a descendant. Selectors that match a decorative wrapper with no tweens.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/cdc16e3d833c21d2. Report an issue: GitHub.