SubtitleEdit/subtitleedit · critical · InvalidOperationException

Failed to start qwen3-tts-server

Error message

Failed to start qwen3-tts-server

What it means

Defensive throw after Process.Start(psi) returns null. On .NET the managed Process.Start only returns null when UseShellExecute is true and no process was launched; here UseShellExecute is false, so this branch is effectively unreachable in normal operation — it guards against a runtime/platform where Start unexpectedly yields null instead of throwing.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCpp.cs:386

            psi.ArgumentList.Add("--port");
            psi.ArgumentList.Add(port.ToString());

            if (OperatingSystem.IsWindows())
            {
                var vulkanPath = Se.Settings.Video.TextToSpeech.Qwen3TtsCppVulkanPath;
                if (string.IsNullOrEmpty(vulkanPath))
                {
                    vulkanPath = Logic.VulkanHelper.TryFindBinFolder();
                }
                if (!string.IsNullOrEmpty(vulkanPath) && psi.EnvironmentVariables["Path"] != null)
                {
                    psi.EnvironmentVariables["Path"] =
                        psi.EnvironmentVariables["Path"]?.TrimEnd(';') + ";" + vulkanPath;
                }
            }

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

            Se.WriteToolsLog($"Qwen3 TTS server starting - PID: {process.Id}, "
                + $"Cmd: {exe} {string.Join(' ', psi.ArgumentList)}");

            var stderrBuffer = new StringBuilder();
            process.ErrorDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (stderrBuffer) stderrBuffer.AppendLine(e.Data);
            };
            process.OutputDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (stderrBuffer) stderrBuffer.AppendLine(e.Data);
            };
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();

            _serverProcess = process;
            _serverPort = port;

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Confirm UseShellExecute is false (it is, in the current code) — if a fork changed it, revert.
  2. Run the same ProcessStartInfo from a standalone harness to see whether Start returns null or throws a real Win32Exception.
  3. If reproducible only in a sandbox, relax the sandbox's process-creation restrictions.
  4. Treat as a code defect if hit on a normal desktop: file a bug, since Start should throw rather than return null here.
Defensive patterns

Strategy: validation

Validate before calling

if (psi.UseShellExecute)
    throw new InvalidOperationException("UseShellExecute must be false for reliable Start.");
if (string.IsNullOrWhiteSpace(psi.FileName))
    throw new InvalidOperationException("Executable path is empty.");

Try / catch

try { var process = Process.Start(psi) ?? throw new InvalidOperationException("Failed to start qwen3-tts-server"); }
catch (System.ComponentModel.Win32Exception ex) { Se.LogError(ex, "Process.Start failed"); throw; }

Prevention

When it happens

Trigger: A future change sets UseShellExecute=true; running under a host/runtime that returns null from Start for an empty argument list; extremely rare runtime quirk where the spawn succeeds enough to return non-throwing but no Process object is produced.

Common situations: Code regression that flips UseShellExecute to true; an exotic container/jail environment where process creation is partially virtualised; effectively never seen in normal desktop use.

Related errors


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