heygen-com/hyperframes · error · Error
hf-seek waitUntil() must be called synchronously
Error message
hf-seek waitUntil() must be called synchronously
What it means
Thrown inside the browser by seekAllAdaptersInBrowser when a composition's hf-seek event handler calls detail.waitUntil(promise) asynchronously — after the dispatchEvent call has returned synchronously. The acceptingGpuWork flag is flipped to false in the finally block the instant dispatchEvent returns, so any waitUntil call outside that synchronous handler window is rejected. This enforces that GPU work registration happens during the event dispatch tick, before the seek routine awaits pending work.
Source
Thrown at packages/cli/src/commands/motionShot.ts:251
for (const instance of w.__hfAnime ?? []) {
tryCall(() => {
instance.pause?.();
instance.seek?.(timeMs);
});
}
w.__hfThreeTime = tt;
if (!runtimeSeeked) {
let acceptingGpuWork = true;
try {
window.dispatchEvent(
new CustomEvent("hf-seek", {
detail: {
time: tt,
waitUntil(promise: PromiseLike<unknown>) {
if (!acceptingGpuWork) {
throw new Error("hf-seek waitUntil() must be called synchronously");
}
pendingGpuWork.push(promise);
},
},
}),
);
} finally {
acceptingGpuWork = false;
}
}
tryCall(() => w.__hfThreeRender?.());
tryCall(() => w.gsap?.ticker?.tick?.());
await Promise.all([Promise.all(pendingGpuWork), w.__hfWaitForSeekCompletion?.()]);
}
// Installs seekAllAdaptersInBrowser as a real `window` global, once per page
// load. Both the ghost-frame capture and the marker sampler then call it via aView on GitHub (pinned to c2996c8626)
Solutions
- Call waitUntil synchronously inside the hf-seek handler, before any await or setTimeout
- Register the GPU-work promise at call time even if the work itself resolves later
- If using an async handler, collect the promise synchronously and defer only the await
- Restructure: do the synchronous registration first, then the async rendering inside the promise
Example fix
// before — waitUntil called after a delay (throws)
window.addEventListener('hf-seek', (e) => {
requestAnimationFrame(() => e.detail.waitUntil(renderGpu()));
});
// after — register synchronously inside the handler
window.addEventListener('hf-seek', (e) => {
e.detail.waitUntil(renderGpu());
}); Defensive patterns
Strategy: validation
Validate before calling
// Composition-authoring rule (runs in the browser):
// Register waitUntil synchronously inside the hf-seek handler.
window.addEventListener('hf-seek', (event) => {
const detail = (event as CustomEvent).detail;
// GOOD: synchronous registration
detail.waitUntil(myGpuRenderPromise);
// BAD (would throw): setTimeout(() => detail.waitUntil(p), 0);
}); Prevention
- Never call waitUntil inside setTimeout, rAF, or after an await
- Keep hf-seek handlers synchronous for the waitUntil registration; do async work inside the promise body
When it happens
Trigger: A composition registers window.addEventListener('hf-seek', e => { ... }) and inside it calls e.detail.waitUntil() inside a setTimeout, requestAnimationFrame, Promise.then, or any deferred callback. Also triggered if waitUntil is called from an async handler after an await.
Common situations: Custom WebGL/three.js adapters that schedule GPU work via rAF or microtasks. Event handlers written as async functions where waitUntil is called after an await. Adapters ported from an async rendering pipeline that defer submission.
Related errors
- --shot: --selector '${scopeSelector}' matched no element.
- --shot: nothing animates under '${scopeSelector}'. Nearest a
- --ghost renders a canvas/WebGL motion trail, but this compos
- ghost composite returned no data
- screenshot returned no data
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/920384b9f4aa0a59.
Report an issue: GitHub.