SubtitleEdit/subtitleedit · error · TimeoutException

crispasr (moss-tts) did not report healthy within {10|60} mi

Error message

crispasr (moss-tts) did not report healthy within {10|60} minutes. Last output: {lastOutput}{LaunchCmdSuffix}

What it means

TimeoutException thrown when the crispasr moss-tts process stays alive but never responds to GET /health within the deadline (10 minutes if the model is already staged locally, 60 minutes if first-run auto-download is in progress). The server is stopped via StopServerInternal before throwing.

Source

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

                    _serverModelKey = null;
                    _serverVoicePath = null;
                    _serverRefText = null;
                    _serverLanguageArg = null;
                    throw new InvalidOperationException(
                        $"crispasr (moss-tts) exited during startup (code {exitCode}). Output: {tail}"
                        + LaunchCmdSuffix(exitedLaunchCommand));
                }
                if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(2), ct))
                {
                    return;
                }
                await Task.Delay(TimeSpan.FromSeconds(1), ct);
            }

            var lastOutput = SnapshotServerLog();
            var timeoutLaunchCommand = _serverLaunchCommand;
            StopServerInternal();
            throw new TimeoutException(
                $"crispasr (moss-tts) did not report healthy within {(hasLocalModel ? 10 : 60)} minutes. Last output: {lastOutput}"
                + LaunchCmdSuffix(timeoutLaunchCommand));
        }
        finally
        {
            ServerLock.Release();
        }
    }

    private static string SnapshotServerLog()
    {
        lock (_serverLog)
        {
            var s = _serverLog.ToString().TrimEnd();
            return s.Length > 2000 ? s[^2000..] : s;
        }
    }

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Check the 'Last output' in the exception — if it shows download progress, the connection is too slow; consider pre-downloading the GGUF manually and placing it in Se.CrispAsrFolder/models.
  2. If the output shows the model loaded but no health line, verify the crispasr version supports the /health endpoint (older builds may not).
  3. Switch to the Q4_K quant (~10.5 GB) to halve load time on first run.
  4. On a machine with enough RAM, ensure the model is on a local SSD, not a network/spinning drive.
  5. If downloading, check for Hugging Face rate limits or use the --auto-download path with a stable connection.

Example fix

// before — fixed 10/60 min deadline
var deadline = DateTime.UtcNow.AddMinutes(hasLocalModel ? 10 : 60);
while (DateTime.UtcNow < deadline)
{
    if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(2), ct)) return;
    await Task.Delay(TimeSpan.FromSeconds(1), ct);
}
throw new TimeoutException(...);

// after — make the deadline configurable for slow machines
var minutes = hasLocalModel
    ? Se.Settings.Video.TextToSpeech.MossTtsStartupTimeoutMinutes
    : 60;
var deadline = DateTime.UtcNow.AddMinutes(minutes);
Defensive patterns

Strategy: retry

Validate before calling

// Check if models are already staged to predict load time
var hasLocalModel = MossTtsCrispAsr.AreModelsInstalled(modelKey);
var expectedMinutes = hasLocalModel ? 10 : 60;
if (estimatedLoadTimeMinutes > expectedMinutes)
    Se.WriteToolsLog($"Warning: model load may exceed the {expectedMinutes} min deadline on this hardware.");

Type guard

null

Try / catch

catch (TimeoutException ex) when (ex.Message.Contains("did not report healthy"))
{
    // The server may still be loading. Retry once with a longer wait,
    // or pre-download the model and retry with hasLocalModel=true (faster deadline path).
    if (attempt == 0) { await Task.Delay(TimeSpan.FromMinutes(2), ct); goto retry; }
    throw;
}

Prevention

When it happens

Trigger: The EnsureServerRunningAsync while-loop runs until DateTime.UtcNow exceeds the deadline without ProbeHealthAsync ever returning true and without the process exiting. The server is presumably loading a large model or downloading it, but 10/60 minutes elapsed without the /health endpoint going green.

Common situations: First-run download of the ~20.5 GB F16 model on a slow or throttled connection; the model is on a slow network drive and disk I/O is the bottleneck; CPU-only inference on an underpowered machine where model load takes exceptionally long; the server is stuck in a retry loop downloading a model (Hugging Face rate limiting); the /health endpoint exists but returns non-200 (ProbeHealthAsync only returns true on IsSuccessStatusCode).

Related errors


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