SubtitleEdit/subtitleedit · error · TimeoutException
qwen3-tts-server did not report healthy within 120s. Last ou
Error message
qwen3-tts-server did not report healthy within 120s. Last output: {lastOutput} What it means
TimeoutException thrown when the qwen3-tts-server stays alive but never answers the /health probe within 120 seconds. The server is explicitly stopped (StopServerInternal) before throwing, and the last captured stderr/output is appended so the user can see where the server stalled.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCpp.cs:436
_serverModelFileName = null;
// An empty tail plus a 0xC00000xx code means the loader killed it before main -
// NativeExitCodeHelper explains those instead of just printing the raw number.
var hint = NativeExitCodeHelper.GetHint(process.ExitCode, "Qwen3 TTS", GetSetFolder());
throw new InvalidOperationException(
$"qwen3-tts-server exited during startup (code {NativeExitCodeHelper.Describe(process.ExitCode)})."
+ (hint == null ? string.Empty : " " + hint)
+ $" Output: {tail}");
}
if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(1), ct))
{
return;
}
await Task.Delay(TimeSpan.FromMilliseconds(500), ct);
}
var lastOutput = SnapshotStderr(stderrBuffer);
StopServerInternal();
throw new TimeoutException(
$"qwen3-tts-server did not report healthy within 120s. Last output: {lastOutput}");
}
finally
{
ServerLock.Release();
}
}
private static string SnapshotStderr(StringBuilder buffer)
{
lock (buffer)
{
var s = buffer.ToString().TrimEnd();
return s.Length > 2000 ? s[^2000..] : s;
}
}
// Tail of the running server's captured stdout/stderr - empty when no server is tracked.View on GitHub (pinned to 17a9f07487)
Solutions
- Read Last output — a server stuck printing init progress is just slow, not broken; a silent log suggests a port/endpoint mismatch.
- Confirm no other qwen3-tts-server is already bound to the loopback port (the code picks a free port, but a stale zombie can race).
- Warm the model once outside the app (run the server manually) to confirm it eventually becomes healthy, then increase the deadline if the platform legitimately needs longer.
- Update ProbeHealthAsync's route if the server build renamed /health.
Defensive patterns
Strategy: retry
Validate before calling
// Confirm the health route exists for this server build before relying on the 120s wait. var routeOk = await ProbeHealthAsync(port, TimeSpan.FromSeconds(1), ct);
Try / catch
try { await EnsureServerRunningAsync(modelFileName, ct); }
catch (TimeoutException ex)
{
// Server alive but slow — surface Last output; offer to retry with a warmed model.
Se.LogError(ex);
throw;
} Prevention
- Warm the model once outside the app to confirm it eventually becomes healthy.
- Keep the health-probe route in sync with the server build.
- Don't leave zombie servers holding the loopback port.
When it happens
Trigger: First-run model load on a slow CPU/disk exceeds 120s; the server is alive but bound to the wrong port or didn't open the expected /health route; GPU init is stuck (e.g. waiting on a driver handoff); the model is being paged in over a slow network mount.
Common situations: Cold start of a large model on integrated graphics; the server's /health endpoint changed name in a newer build so ProbeHealthAsync always 404s; another instance of the server already holds the port; very slow disk where mmap-loading the GGUF takes minutes.
Related errors
- Qwen3 TTS request failed: {ex.Message}{ServerOutput}
- qwen3-tts-server exited during startup (code {NativeExitCode
- crispasr (qwen3-tts) did not report healthy within {hasLocal
- crispasr (moss-tts) did not report healthy within {10|60} mi
- crispasr (omnivoice) did not report healthy within {5|30} mi
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/f386b4a3b05b4afe.
Report an issue: GitHub.