{"record":{"id":"2f05578c41a11610","repo":"moeru-ai/airi","slug":"errorcode-unexpected-output-action-output","errorCode":null,"errorMessage":"[${errorCode}] Unexpected output action: ${output.action}","messagePattern":"\\[(.+?)\\] Unexpected output action: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/inference/adapters/kokoro.ts","lineNumber":461,"sourceCode":"      )\n\n      worker.postMessage({\n        type: 'run-inference',\n        requestId,\n        input: { action: 'generate', text, voice },\n      })\n\n      const response = await resultPromise\n      const output = response.output\n\n      if (output.action === 'generate') {\n        state = 'ready'\n        onSuccess()\n        return encodeWav(output.samples as Float32Array, output.samplingRate as number)\n      }\n\n      const errorCode = classifyError(new Error('Unexpected output action'))\n      throw new Error(`[${errorCode}] Unexpected output action: ${output.action}`)\n    }), { text: text.slice(0, 50), voice }).catch((error) => {\n      if (error === notReadyError)\n        throw error\n\n      // Cancellation is a caller-controlled lifecycle outcome, not a worker\n      // failure. Keep the loaded model available and avoid restarting the\n      // worker after waitForWorkerMessage has already posted `cancel`.\n      if ((error as Error)?.name === 'AbortError') {\n        if (state === 'running')\n          state = 'ready'\n        throw error\n      }\n\n      handleWorkerError(error instanceof Error ? error : new Error(String(error)))\n      throw error\n    })\n  }\n","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/libs/inference/adapters/kokoro.ts#L443-L479","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// before generate(), ensure the model is ready\nif (!worker || state !== 'ready') throw notReadyError\n// the action check is inherent to the worker protocol; keep worker and adapter versions aligned","typeGuard":"function isGenerateOutput(output: unknown): output is { action: 'generate', samples: Float32Array, samplingRate: number } {\n  return !!output && typeof output === 'object'\n    && (output as { action?: string }).action === 'generate'\n}","tryCatchPattern":"try {\n  const wav = await adapter.generate(text, voice, { signal })\n}\ncatch (err) {\n  if (err instanceof Error && err.message.includes('Unexpected output action')) {\n    // worker/adapter protocol mismatch — reload model, realign versions; do not retry unchanged\n  }\n  else if ((err as Error)?.name === 'AbortError') {\n    // caller cancellation; do not treat as failure\n  }\n  else throw err\n}","preventionTips":["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)."],"tags":["kokoro","tts","worker","web-worker","inference","protocol-mismatch"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}