{"record":{"id":"6cafc472f219a4e0","repo":"continuedev/continue","slug":"error-killing-tts-process","errorCode":null,"errorMessage":"Error killing TTS process: ","messagePattern":"Error killing TTS process: ","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/util/tts.ts","lineNumber":46,"sourceCode":"\n  message = message.trim().replace(/\\s+/g, \" \");\n\n  return message;\n}\n\nexport class TTS {\n  static os: string | undefined = undefined;\n  static handle: ChildProcess | undefined = undefined;\n  static messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>;\n\n  static async read(message: string) {\n    message = sanitizeMessageForTTS(message);\n\n    try {\n      // Kill any active TTS processes\n      await TTS.kill();\n    } catch (e) {\n      console.warn(\"Error killing TTS process: \", e);\n      return;\n    }\n\n    switch (TTS.os) {\n      case \"darwin\":\n        TTS.handle = exec(`say \"${message}\"`);\n        break;\n      case \"win32\":\n        // Replace single quotes on windows\n        TTS.handle = exec(\n          `powershell -Command \"Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('${message.replace(\n            /'/g,\n            \"''\",\n          )}')\"`,\n        );\n        break;\n      case \"linux\":\n        TTS.handle = exec(`espeak \"${message}\"`);","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/continuedev/continue/blob/5522c6f44ca0ac3528b37244818fbfa39b5af470/core/util/tts.ts#L28-L64","documentation":"This warning is emitted by the TTS (text-to-speech) read function when it tries to kill a previously active speech process before starting a new utterance, and that kill operation fails. It uses core/util/tts.ts's TTS.kill() to terminate the prior say/espeak-style child process. The read call then returns early, so the new message is never spoken.","triggerScenarios":"Calling the public read() (driven by llmStreamChat / done handlers) while a previous TTS utterance is still playing, and the underlying child process handle is stale, already exited, or the OS command spawn fails (e.g. say not found on non-darwin, kill signal rejected).","commonSituations":"Rapid consecutive TTS reads on streamed LLM responses; process handle leaked from a crashed earlier spawn; running on a platform where the configured TTS binary is missing or has wrong permissions.","solutions":["Check which TTS command is selected for your OS (TTS.os switch) and verify it is installed (e.g. `say` on macOS, `espeak`/`festival` on Linux)","Avoid issuing read() calls while a previous utterance is active, or debounce/drain TTS requests on the client side","Make TTS.kill() tolerant of already-exited processes (check handle.exitCode !== null before killing) so a stale handle does not abort the new read","If the process handle is unrecoverable, reset TTS.handle to null in the catch so subsequent reads are not permanently blocked"],"exampleFix":"// before\ntry {\n  await TTS.kill();\n} catch (e) {\n  console.warn(\"Error killing TTS process: \", e);\n  return;\n}\n\n// after\ntry {\n  await TTS.kill();\n} catch (e) {\n  console.warn(\"Error killing TTS process: \", e);\n  TTS.handle = null; // recover instead of aborting the new utterance\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await tts.read(message);\n} catch (e) {\n  // non-fatal: log and continue the chat stream\n  console.warn(\"TTS unavailable:\", e);\n}","preventionTips":["Verify the OS TTS binary (say/espeak) is installed before enabling voice output","Debounce read() calls so a new utterance is only issued after the previous completes","Treat TTS failures as non-fatal; never let them break the LLM stream loop"],"tags":["tts","child-process","audio","cleanup"],"backgroundTag":"child-process-kill-failed","analyzedSha":"5522c6f44ca0ac3528b37244818fbfa39b5af470","analyzedAt":"2026-08-27T11:28:54.683Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}