moeru-ai/airi · error · Error
[${errorCode}] Unexpected output action: ${output.action}
Error message
[${errorCode}] Unexpected output action: ${output.action} What it means
Thrown in generate() when the inference worker returns an output object whose `action` field is not 'generate'. The adapter only knows how to handle generate-action outputs (PCM samples); any other action means the worker and adapter have diverged on the inference-result protocol. The message is prefixed with a classifyError code for telemetry routing.
Source
Thrown at packages/stage-ui/src/libs/inference/adapters/kokoro.ts:461
)
worker.postMessage({
type: 'run-inference',
requestId,
input: { action: 'generate', text, voice },
})
const response = await resultPromise
const output = response.output
if (output.action === 'generate') {
state = 'ready'
onSuccess()
return encodeWav(output.samples as Float32Array, output.samplingRate as number)
}
const errorCode = classifyError(new Error('Unexpected output action'))
throw new Error(`[${errorCode}] Unexpected output action: ${output.action}`)
}), { text: text.slice(0, 50), voice }).catch((error) => {
if (error === notReadyError)
throw error
// Cancellation is a caller-controlled lifecycle outcome, not a worker
// failure. Keep the loaded model available and avoid restarting the
// worker after waitForWorkerMessage has already posted `cancel`.
if ((error as Error)?.name === 'AbortError') {
if (state === 'running')
state = 'ready'
throw error
}
handleWorkerError(error instanceof Error ? error : new Error(String(error)))
throw error
})
}
View on GitHub (pinned to 27111382b4)
Solutions
- Ensure the Kokoro worker bundle and adapter are from the same version (protocol contracts must match).
- Inspect the actual output.action value (it is in the message) to identify which worker path produced it.
- If a new legitimate action was added, extend the adapter's if/else to handle it before this throw.
- Reload the model and retry once to rule out a transient worker state corruption.
Defensive patterns
Strategy: try-catch
Validate before calling
// before generate(), ensure the model is ready if (!worker || state !== 'ready') throw notReadyError // the action check is inherent to the worker protocol; keep worker and adapter versions aligned
Type guard
function isGenerateOutput(output: unknown): output is { action: 'generate', samples: Float32Array, samplingRate: number } {
return !!output && typeof output === 'object'
&& (output as { action?: string }).action === 'generate'
} Try / catch
try {
const wav = await adapter.generate(text, voice, { signal })
}
catch (err) {
if (err instanceof Error && err.message.includes('Unexpected output action')) {
// worker/adapter protocol mismatch — reload model, realign versions; do not retry unchanged
}
else if ((err as Error)?.name === 'AbortError') {
// caller cancellation; do not treat as failure
}
else throw err
} Prevention
- Pin the Kokoro worker bundle and adapter to the same release.
- Handle new worker actions in the adapter before shipping the worker that emits them.
- Do not treat AbortError as a worker failure (the adapter already separates these).
When it happens
Trigger: waitForWorkerMessage for 'inference-result' resolves with response.output.action !== 'generate'. Caused by a worker that emits a different action (e.g. a partial/progress result, an error-shaped result, or a new protocol action the adapter does not understand).
Common situations: Worker bundle updated to a protocol version that adds new action types the adapter does not handle. A worker bug emitting an unexpected action on success. Model/quantization producing a result shape the adapter did not anticipate.
Related errors
- Kokoro worker did not return voice metadata
- Web worker transport is not available in node runtime.
- Web worker transport is not implemented yet.
- Missing input text for Gemini TTS
- Missing model for Gemini TTS
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/2f05578c41a11610.
Report an issue: GitHub.