heygen-com/hyperframes · info · AudioFxRenderError

Audio FX cancelled for track ${options.trackId}

Error message

Audio FX cancelled for track ${options.trackId}

What it means

Thrown when options.signal?.aborted is true at the checkpoint inside applyAudioFxChain(), which runs after acquiring a browser lease but before opening a page. This is an intentional cancellation — the caller's AbortSignal fired, so the FX render is aborted cleanly rather than continuing to consume browser resources.

Source

Thrown at packages/engine/src/services/audioFxRender.ts:217

): Promise<string> {
  if (enabledAudioFxNodes(chain).length === 0) return inputWav;
  if (!existsSync(inputWav)) {
    throw new AudioFxRenderError(`Audio FX input is missing: ${inputWav}`);
  }

  const { samples, sampleRate, channels } = readWav(inputWav);
  const planes = deinterleave(samples, channels);

  // Audio processing needs no GPU or special capture mode; a plain sandboxed
  // browser is enough, and the lease pool reuses one across tracks.
  const lease = await acquireBrowser([
    "--no-sandbox",
    "--autoplay-policy=no-user-gesture-required",
  ]);
  const hostDir = mkdtempSync(join(tmpdir(), "hf-fx-host-"));
  try {
    if (options.signal?.aborted) {
      throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
    }
    const page = await lease.browser.newPage();
    try {
      // AudioWorklet is only exposed in a secure context, and about:blank is
      // not one — the module would fail with an opaque error. A file:// page
      // qualifies and needs no listening socket.
      const hostPage = join(hostDir, "audio-fx.html");
      writeFileSync(hostPage, "<!doctype html><meta charset=utf-8><title>audio fx</title>");
      await page.goto(pathToFileURL(hostPage).href, { waitUntil: "domcontentloaded" });
      await page.addScriptTag({ content: getAudioFxRuntimeScript() });

      const rendered = (await page.evaluate(
        async ([channelB64, rate, chainJson]: [string[], number, string]) => {
          const decode = (b64: string): Float32Array => {
            const bin = atob(b64);
            const bytes = new Uint8Array(bin.length);
            for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
            return new Float32Array(bytes.buffer);

View on GitHub (pinned to c2996c8626)

Solutions

  1. This is expected behavior for cancellation — verify the AbortController was intentionally triggered and propagate the cancellation upstream.
  2. If the abort is unexpected, trace which code called controller.abort() and under what condition.
  3. Ensure the caller handles this as a cancellation (not a hard failure) so partial output is cleaned up.
Defensive patterns

Strategy: try-catch

Validate before calling

if (options.signal?.aborted) {
  // skip the FX chain entirely, don't acquire a browser
  return inputWav;
}

Try / catch

try {
  await applyAudioFxChain(inputWav, chain, outWav, opts);
} catch (err) {
  if (err instanceof AudioFxRenderError && err.message.includes('cancelled')) {
    // graceful cancellation — clean up partial output, propagate AbortError
    throw new DOMException('Aborted', 'AbortError');
  }
  throw err;
}

Prevention

When it happens

Trigger: applyAudioFxChain() is called with options.signal set to an AbortSignal that becomes aborted between the existsSync check and the post-acquireBrowser checkpoint. The throw happens inside the try block so the finally clauses release the browser lease and clean up the temp host directory.

Common situations: User cancels a render job mid-track. A pipeline-level timeout fires. A parent render orchestration aborts all child tasks on the first error. The AbortController is connected to a request deadline in a serverless function.

Related errors


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