{"record":{"id":"0ab25edf67f4a72c","repo":"moeru-ai/airi","slug":"llm-streaming-control-signal-handler-failed","errorCode":null,"errorMessage":"[llm-streaming-control] signal handler failed","messagePattern":"\\[llm-streaming-control\\] signal handler failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/pipelines-audio/src/llm-streaming-control/controller.ts","lineNumber":314,"sourceCode":"\n      const signalContext: LlmStreamingControlSignalContext = {\n        ...dispatchContext,\n        createdAt: Date.now(),\n      }\n\n      // snapshot prevents mutation during iteration\n      for (const handler of [...signalHandlers]) {\n        try {\n          await handler(parsed, signalContext)\n        }\n        catch (error) {\n          emit(context, {\n            type: 'signal-handler-error',\n            tokenType: parsed.type,\n            error,\n          })\n\n          console.warn(\n            '[llm-streaming-control] signal handler failed',\n            error,\n          )\n        }\n      }\n\n      if (parsed.type !== 'call')\n        return true\n\n      const turnState = dispatchContext.turnId\n        ? turns.get(dispatchContext.turnId)\n        : undefined\n\n      const turnHandlers = turnState?.handlers.get(parsed.name)\n\n      const activeHandlers\n        = turnHandlers?.size && turnState\n          ? turnHandlers","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/pipelines-audio/src/llm-streaming-control/controller.ts#L296-L332","documentation":"The llm-streaming-control controller parses control tokens ('signals') embedded in the LLM output stream and awaits each registered signal handler in turn, each inside its own try/catch. When one handler rejects, the error is emitted as a 'signal-handler-error' event (with tokenType) and logged here; iteration continues with the remaining handlers, so a single bad handler degrades only its own side effect.","triggerScenarios":"An onSignal-registered handler throwing on a specific parsed token shape: assuming a field exists on a signal type that doesn't have it, or a side effect (UI update, audio scheduling) raising for certain payloads.","commonSituations":"A handler written against one token schema while the model emits another variant; state accessed before initialization inside the handler; async side effects failing transiently.","solutions":["Subscribe to the 'signal-handler-error' event to capture tokenType and the error for the failing handler","Validate the parsed payload shape at the top of each handler and return early for unexpected types","Wrap fallible side effects (audio, network, DOM) inside the handler with their own try/catch so the handler itself never rejects"],"exampleFix":"// before\ncontroller.onSignal(async (parsed) => {\n  await scheduleAudioCue(parsed.cue) // throws when parsed has no cue field\n})\n\n// after\ncontroller.onSignal(async (parsed) => {\n  if (parsed.type !== 'cue')\n    return\n  try {\n    await scheduleAudioCue(parsed.cue)\n  }\n  catch (error) {\n    console.warn('audio cue skipped', error)\n  }\n})","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isCueSignal(parsed: unknown): parsed is { type: 'cue', cue: string } {\n  return typeof parsed === 'object' && parsed !== null && 'cue' in parsed && parsed.type === 'cue'\n}","tryCatchPattern":"// inside your signal handler\ntry {\n  await applySignalSideEffect(parsed)\n}\ncatch (error) {\n  // report without letting the controller see a rejection\n  console.warn('signal side effect failed for', parsed?.type, error)\n}","preventionTips":["Subscribe to the 'signal-handler-error' event to observe handler failures with tokenType","Narrow the parsed token shape before acting on it","Never let side effects (audio, network, DOM) reject the handler itself"],"tags":["llm","streaming","event-handler","signal-parsing"],"backgroundTag":"event-handler-exception","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}