SubtitleEdit/subtitleedit · error · InvalidOperationException

Failed to start crispasr (indextts)

Error message

Failed to start crispasr (indextts)

What it means

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.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/IndexTtsCrispAsr.cs:575

            {
                psi.ArgumentList.Add("--auto-download");
            }
            psi.ArgumentList.Add("--host");
            psi.ArgumentList.Add("127.0.0.1");
            psi.ArgumentList.Add("--port");
            psi.ArgumentList.Add(port.ToString());
            // /v1/audio/speech gates `voice` field on --voice-dir being set. Same as Qwen3.
            psi.ArgumentList.Add("--voice-dir");
            psi.ArgumentList.Add(GetSetVoicesFolder());
            // Required for indextts: the server-mode voice cloning only respects the startup
            // --voice path. Skipping this means the request's `voice` is ignored and the synth
            // produces noise. See CrispASR 0.6.11 upstream bug.
            psi.ArgumentList.Add("--voice");
            psi.ArgumentList.Add(voicePath);
            CrispAsrTtsProvenance.AddServerMarkingArgs(psi.ArgumentList, exe);

            var process = Process.Start(psi)
                ?? throw new InvalidOperationException("Failed to start crispasr (indextts)");

            var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList);
            _serverLaunchCommand = launchCommand;
            Se.WriteToolsLog("IndexTTS (CrispASR) server starting — "
                + $"PID: {process.Id}, "
                + $"Cmd: {launchCommand}");

            lock (_serverLog) _serverLog.Clear();
            process.ErrorDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);
            };
            process.OutputDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);
            };
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Treat this as 'spawn failed for an unknown reason' and check exe path, permissions, and PATH.
  2. Confirm GetCrispAsrExecutable() exists and is executable.
  3. Run the exact launch command (logged just after) by hand to see the real OS error.
  4. Update the .NET runtime if you genuinely hit a null return, then file a bug.
Defensive patterns

Strategy: try-catch

Validate before calling

// Defensive: verify exe before spawn (real failures throw Win32Exception, not null)
if (!File.Exists(exe)) { throw new FileNotFoundException("exe missing", exe); }

Try / catch

try { await indexEngine.Speak(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Failed to start crispasr"))
{
    // Rare; fall back to manual reproduction of the logged launch command.
    ShowLaunchCommand(ex.Message);
}

Prevention

When it happens

Trigger: 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.

Common situations: Observed only on runtimes/shims that return null instead of throwing; otherwise a dead branch.

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/df6020a34604dc48. Report an issue: GitHub.