moeru-ai/airi · error · Error

Worklet loading failed

Error message

Worklet loading failed: ${err}

What it means

Thrown by loadWorklets() inside initializeAudioContext (packages/audio) when context.audioWorklet.addModule() rejects for either the processor or the libsamplerate worklet module. The underlying error is interpolated into the message and workletLoaded stays false, so the context is unusable for AudioWorkletNode processing until a later initializeAudioContext call succeeds.

Solutions

  1. Read the wrapped error after 'Worklet loading failed:' — it names the exact addModule failure — and open the worklet URL directly (curl or browser tab) to check it returns 200 with a JavaScript MIME type
  2. If 404: make sure the worklet assets are copied to the build output and the URL is derived from import.meta.url / the correct public base
  3. If CSP-blocked: allow the worklet origin in worker-src and script-src (e.g. worker-src 'self' blob:)
  4. If the module throws on evaluation: import the worklet file in a plain script context to surface the syntax/import error
  5. Retry initializeAudioContext() after fixing — workletLoaded remains false so the load is reattempted

Example fix

// before
await initializeAudioContext(48000) // Error: Worklet loading failed: ...

// after
try {
  await initializeAudioContext(48000)
}
catch (err) {
  console.error('audio init failed, retrying once assets settle:', err)
  await new Promise(r => setTimeout(r, 500))
  await initializeAudioContext(48000) // workletLoaded stayed false, so loading is retried
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the worklet asset URLs your build actually serves are reachable before init
for (const url of workletAssetUrls) {
  const res = await fetch(url)
  if (!res.ok || !(res.headers.get('content-type') ?? '').includes('javascript'))
    console.warn(`worklet asset problem: ${url} -> ${res.status}`)
}

Try / catch

try {
  await initializeAudioContext(48000)
}
catch (err) {
  // workletLoaded stays false, so a retry after fixing assets is safe
  if (err instanceof Error && err.message.startsWith('Worklet loading failed')) {
    reportAudioInitFailure(err) // inspect the wrapped cause, then retry or degrade gracefully
  }
  throw err
}

Prevention

When it happens

Trigger: Calling initializeAudioContext() when ProcessorWorkletURL or LibsamplerateWorkletURL returns 404, is blocked by page CSP (worker-src/script-src), is served with a non-JavaScript MIME type, or when the worklet module itself throws during evaluation (syntax error, bad import).

Common situations: Bundler/dev-server does not emit the worklet files into build output; wrong base path so the URL resolves off-origin; strict Content-Security-Policy in Electron; CORS failure loading worklets from a CDN; worklet file references an identifier that does not exist in worklet scope.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/96cd9d205bfd43c4. Report an issue: GitHub.

Appendix: source

Thrown at packages/audio/src/audio-context/index.ts:73

    catch (err) {
      console.error('AudioContext state listener error:', err)
    }
  })
}

async function loadWorklets() {
  if (!context || workletLoaded)
    return

  try {
    await context.audioWorklet.addModule(ProcessorWorkletURL)
    await context.audioWorklet.addModule(LibsamplerateWorkletURL)

    workletLoaded = true
  }
  catch (err) {
    console.error('Failed to load AudioWorklets:', err)
    throw new Error(`Worklet loading failed: ${err}`)
  }
}

export async function initializeAudioContext(requestedSampleRate: number = 48000): Promise<AudioContext> {
  // Use high quality base sample rate
  const baseSampleRate = Math.max(requestedSampleRate, 48000)

  if (context && isReady && sampleRate === baseSampleRate && workletLoaded) {
    return context
  }

  if (isInitializing) {
    return new Promise((resolve, reject) => {
      const checkReady = () => {
        if (!isInitializing) {
          if (context && isReady && workletLoaded) {
            resolve(context)
          }

View on GitHub (pinned to 677329427f)