SubtitleEdit/subtitleedit · error · InvalidOperationException

Failed to start crispasr (moss-tts)

Error message

Failed to start crispasr (moss-tts)

What it means

InvalidOperationException thrown if Process.Start(psi) returns null. In practice this is nearly unreachable: with UseShellExecute=false (as configured here), Process.Start either returns a non-null Process object or throws a Win32Exception. The null-check is a defensive guard for the theoretical shell-execute case or a future framework change.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/MossTtsCrispAsr.cs:701

            if (!string.IsNullOrEmpty(languageArg))
            {
                psi.ArgumentList.Add("-l");
                psi.ArgumentList.Add(languageArg);
            }

            if (UseStartupVoiceFlags)
            {
                psi.ArgumentList.Add("--voice");
                psi.ArgumentList.Add(voicePath);
                if (!string.IsNullOrEmpty(refText))
                {
                    psi.ArgumentList.Add("--ref-text");
                    psi.ArgumentList.Add(refText);
                }
            }

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

            var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList);
            _serverLaunchCommand = launchCommand;
            Se.WriteToolsLog("MOSS-TTS (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. Check whether the ProcessStartInfo.UseShellExecute flag was inadvertently changed to true — with false, this throw path is dead code.
  2. If genuinely null from the framework, file a dotnet/runtime issue and temporarily retry the Process.Start call.
  3. Verify the executable path and arguments are valid (a bad FileName throws Win32Exception, not null, but sanity-check anyway).
  4. Log the psi contents to Tools log to capture the exact state at the time of failure for diagnosis.

Example fix

// before — defensive null-coalesce that is practically dead code
var process = Process.Start(psi)
    ?? throw new InvalidOperationException("Failed to start crispasr (moss-tts)");

// after — catch the actual failure modes (Win32Exception) instead
try { var process = Process.Start(psi); }
catch (Win32Exception ex)
{
    throw new InvalidOperationException(
        $"Failed to start crispasr (moss-tts): {ex.Message}. " +
        "Check the executable exists and is not blocked.", ex);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the binary exists and is executable before Process.Start
var exe = GetCrispAsrExecutable();
if (!File.Exists(exe)) throw new FileNotFoundException("Missing: " + exe, exe);

Type guard

null

Try / catch

catch (InvalidOperationException ex) when (ex.Message == "Failed to start crispasr (moss-tts)")
{
    // Practically unreachable; the real error is Win32Exception.
    // Log the psi state and fall through to a generic handler.
    Se.LogError(ex, "Process.Start returned null unexpectedly.");
    throw;
}

Prevention

When it happens

Trigger: Process.Start(psi) returns null. Given UseShellExecute=false in the ProcessStartInfo, the only realistic path is a CLR/framework edge case or a future API change. Under normal conditions, if the binary cannot start, a Win32Exception (not null) is thrown instead.

Common situations: Essentially unreachable in production with the current ProcessStartInfo configuration. If it does surface, it indicates a CLR bug, a broken Process.Start shim, or code that was refactored to set UseShellExecute=true without updating this guard.

Related errors


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