{"record":{"id":"920384b9f4aa0a59","repo":"heygen-com/hyperframes","slug":"hf-seek-waituntil-must-be-called-synchronously","errorCode":null,"errorMessage":"hf-seek waitUntil() must be called synchronously","messagePattern":"hf-seek waitUntil\\(\\) must be called synchronously","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/motionShot.ts","lineNumber":251,"sourceCode":"\n  for (const instance of w.__hfAnime ?? []) {\n    tryCall(() => {\n      instance.pause?.();\n      instance.seek?.(timeMs);\n    });\n  }\n\n  w.__hfThreeTime = tt;\n  if (!runtimeSeeked) {\n    let acceptingGpuWork = true;\n    try {\n      window.dispatchEvent(\n        new CustomEvent(\"hf-seek\", {\n          detail: {\n            time: tt,\n            waitUntil(promise: PromiseLike<unknown>) {\n              if (!acceptingGpuWork) {\n                throw new Error(\"hf-seek waitUntil() must be called synchronously\");\n              }\n              pendingGpuWork.push(promise);\n            },\n          },\n        }),\n      );\n    } finally {\n      acceptingGpuWork = false;\n    }\n  }\n  tryCall(() => w.__hfThreeRender?.());\n  tryCall(() => w.gsap?.ticker?.tick?.());\n\n  await Promise.all([Promise.all(pendingGpuWork), w.__hfWaitForSeekCompletion?.()]);\n}\n\n// Installs seekAllAdaptersInBrowser as a real `window` global, once per page\n// load. Both the ghost-frame capture and the marker sampler then call it via a","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/motionShot.ts#L233-L269","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before — waitUntil called after a delay (throws)\nwindow.addEventListener('hf-seek', (e) => {\n  requestAnimationFrame(() => e.detail.waitUntil(renderGpu()));\n});\n// after — register synchronously inside the handler\nwindow.addEventListener('hf-seek', (e) => {\n  e.detail.waitUntil(renderGpu());\n});","handlingStrategy":"validation","validationCode":"// Composition-authoring rule (runs in the browser):\n// Register waitUntil synchronously inside the hf-seek handler.\nwindow.addEventListener('hf-seek', (event) => {\n  const detail = (event as CustomEvent).detail;\n  // GOOD: synchronous registration\n  detail.waitUntil(myGpuRenderPromise);\n  // BAD (would throw): setTimeout(() => detail.waitUntil(p), 0);\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["motion-shot","hf-seek","synchronous","gpu-work","browser-runtime"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}