moeru-ai/airi · error · Error

Kokoro TTS generation failed: No model loaded.

Error message

Kokoro TTS generation failed: No model loaded.

What it means

The Kokoro TTS web worker keeps ttsModel null until its async init (weights download plus WASM/ONNX setup) completes; a 'generate' action arriving before that hits this guard instead of crashing on ttsModel.generate. Note that getVoices answers from the static voice list, so a successful voices response does not imply the model is loaded.

Source

Thrown at packages/stage-ui/src/workers/kokoro/worker.ts:251

        throw new Error('Model not loaded. Send load-model first.')

      if (isCancelled(requestId)) {
        clearCancelled(requestId)
        return
      }

      const result: InferenceResultResponse<KokoroVoicesOutput> = {
        type: 'inference-result',
        requestId,
        output: { action: 'getVoices', voices: ttsModel.voices },
      }
      globalThis.postMessage(result)
      return
    }

    // action === 'generate'
    if (!ttsModel)
      throw new Error('Kokoro TTS generation failed: No model loaded.')

    const { text, voice } = input
    const audioResult = await ttsModel.generate(text, { voice })

    if (isCancelled(requestId)) {
      clearCancelled(requestId)
      return
    }

    // Transfer raw PCM Float32Array directly — avoids WAV blob encode/decode overhead.
    const samples = audioResult.audio
    const result: InferenceResultResponse<KokoroGenerateOutput> = {
      type: 'inference-result',
      requestId,
      output: { action: 'generate', samples, samplingRate: audioResult.sampling_rate },
    }
    ;(globalThis as any).postMessage(result, [samples.buffer])
  }

View on GitHub (pinned to 677329427f)

Solutions

  1. Await the worker's init/ready response before enabling generation.
  2. Treat any init error response as fatal for the session and re-init before the next generate.
  3. Queue generate requests until ready instead of posting them eagerly.
  4. If the weights download failed, clear the model cache and re-init.

Example fix

// before
worker.postMessage({ type: 'inference', requestId, input: { action: 'generate', text, voice } })

// after
await kokoroReady // promise resolved by the init-result message
worker.postMessage({ type: 'inference', requestId, input: { action: 'generate', text, voice } })
Defensive patterns

Strategy: validation

Validate before calling

let ready = false
worker.addEventListener('message', (e) => {
  if (e.data?.type === 'inference-result' && e.data.output?.action === 'init')
    ready = true
})
if (!ready)
  await initPromise

Try / catch

try {
  await generate(text, voice)
}
catch (e) {
  if (errorMessageFrom(e)?.includes('No model loaded')) {
    await initWorker()
    return await generate(text, voice) // single retry after re-init
  }
  throw e
}

Prevention

When it happens

Trigger: UI posts an inference 'generate' immediately after spawning the worker; an earlier init failed (its error response was missed) and a later generate is sent; the worker was restarted by page reload or HMR while UI state still says ready.

Common situations: First-use race on slow networks; HMR during development resetting worker state; init error handling not wired so failures stay silent until generate.

Related errors


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