SubtitleEdit/subtitleedit · error · TimeoutException

kokoro-tts-server did not report healthy within 60s. Last ou

Error message

kokoro-tts-server did not report healthy within 60s. Last output: {lastOutput}

What it means

TimeoutException from KokoroTtsCpp.EnsureServerRunningAsync when the kokoro-tts-server starts and stays alive but does not answer `/health` within 60 seconds. The server is stopped and the last 2000 chars of combined stdout/stderr are attached. Kokoro has no large auto-download, so the deadline is a flat 60s — slower hardware or heavy model load can exhaust it.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/KokoroTtsCpp.cs:378

                    _serverPort = 0;
                    // 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, "Kokoro TTS", GetSetFolder());
                    throw new InvalidOperationException(
                        $"kokoro-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(
                $"kokoro-tts-server did not report healthy within 60s. 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;
        }
    }

    private static async Task<bool> ProbeHealthAsync(int port, TimeSpan timeout, CancellationToken ct)

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Inspect `Last output:` for the server's load progress.
  2. Run the server manually to measure real init time; if it consistently exceeds 60s on your hardware, pre-warm the model.
  3. Disable AV scanning of the models folder.
  4. Move models to faster storage (SSD).
  5. Free CPU/disk contention during startup.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-warm the model so the 60s window is enough on slow disks
await kokoroEngine.PreWarmAsync(ct); // touch/mmap the model once

Try / catch

try { await kokoroEngine.Speak(...); }
catch (TimeoutException ex) when (ex.Message.Contains("did not report healthy within 60s"))
{
    if (await ConfirmRetry()) await kokoroEngine.Speak(...);
}

Prevention

When it happens

Trigger: The server is alive but `/health` is unresponsive (slow model mmap load on a cold disk); loopback firewall blocking the health GET; the server is waiting on a missing dependency and hung (not exited); concurrent disk/CPU contention.

Common situations: First run with a cold OS file cache so model load is slow; antivirus scanning the model on open; slow HDD; heavy background I/O.

Related errors


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