{"record":{"id":"df6020a34604dc48","repo":"SubtitleEdit/subtitleedit","slug":"failed-to-start-crispasr-indextts","errorCode":null,"errorMessage":"Failed to start crispasr (indextts)","messagePattern":"Failed to start crispasr \\(indextts\\)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/IndexTtsCrispAsr.cs","lineNumber":575,"sourceCode":"            {\n                psi.ArgumentList.Add(\"--auto-download\");\n            }\n            psi.ArgumentList.Add(\"--host\");\n            psi.ArgumentList.Add(\"127.0.0.1\");\n            psi.ArgumentList.Add(\"--port\");\n            psi.ArgumentList.Add(port.ToString());\n            // /v1/audio/speech gates `voice` field on --voice-dir being set. Same as Qwen3.\n            psi.ArgumentList.Add(\"--voice-dir\");\n            psi.ArgumentList.Add(GetSetVoicesFolder());\n            // Required for indextts: the server-mode voice cloning only respects the startup\n            // --voice path. Skipping this means the request's `voice` is ignored and the synth\n            // produces noise. See CrispASR 0.6.11 upstream bug.\n            psi.ArgumentList.Add(\"--voice\");\n            psi.ArgumentList.Add(voicePath);\n            CrispAsrTtsProvenance.AddServerMarkingArgs(psi.ArgumentList, exe);\n\n            var process = Process.Start(psi)\n                ?? throw new InvalidOperationException(\"Failed to start crispasr (indextts)\");\n\n            var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList);\n            _serverLaunchCommand = launchCommand;\n            Se.WriteToolsLog(\"IndexTTS (CrispASR) server starting — \"\n                + $\"PID: {process.Id}, \"\n                + $\"Cmd: {launchCommand}\");\n\n            lock (_serverLog) _serverLog.Clear();\n            process.ErrorDataReceived += (_, e) =>\n            {\n                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);\n            };\n            process.OutputDataReceived += (_, e) =>\n            {\n                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);\n            };\n            process.BeginErrorReadLine();\n            process.BeginOutputReadLine();","sourceCodeStart":557,"sourceCodeEnd":593,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/IndexTtsCrispAsr.cs#L557-L593","documentation":"InvalidOperationException from IndexTtsCrispAsr.EnsureServerRunningAsync when `Process.Start(psi)` returns null. On modern .NET Process.Start for a real process effectively never returns null (it throws on failure), so this throw is a defensive guard for a theoretical null return — in practice you will more often see a Win32Exception first. If reached, it means the OS refused to spawn the configured exe/args combination without a thrown exception.","triggerScenarios":"Essentially unreachable on current .NET; historically could surface if the runtime shim returned null for a missing executable. The real failure modes (missing exe, bad path) throw FileNotFoundException/Win32Exception before this line.","commonSituations":"Observed only on runtimes/shims that return null instead of throwing; otherwise a dead branch.","solutions":["Treat this as 'spawn failed for an unknown reason' and check exe path, permissions, and PATH.","Confirm GetCrispAsrExecutable() exists and is executable.","Run the exact launch command (logged just after) by hand to see the real OS error.","Update the .NET runtime if you genuinely hit a null return, then file a bug."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Defensive: verify exe before spawn (real failures throw Win32Exception, not null)\nif (!File.Exists(exe)) { throw new FileNotFoundException(\"exe missing\", exe); }","typeGuard":null,"tryCatchPattern":"try { await indexEngine.Speak(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Failed to start crispasr\"))\n{\n    // Rare; fall back to manual reproduction of the logged launch command.\n    ShowLaunchCommand(ex.Message);\n}","preventionTips":["Pre-validate exe existence and executability before calling Speak.","Log the full launch command (the code already does) to reproduce manually.","Keep the .NET runtime current; a real null return is an upstream bug.","On spawn failures, check PATH/permissions before retrying."],"tags":["indextts","crispasr","process-start","tts"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}