SubtitleEdit/subtitleedit · error · TimeoutException

STT request timed out after {_settings.TimeoutSeconds} secon

Error message

STT request timed out after {_settings.TimeoutSeconds} seconds.

What it means

The engine's own timeoutCTS fires (CancelAfter TimeoutSeconds) before the OpenAI-compatible server returns, and it is provably not a user-initiated cancel (the `when (!cancellationToken.IsCancellationRequested)` guard). The OperationCanceledException is rewrapped as TimeoutException so callers cannot mistake it for the user cancelling the job.

Source

Thrown at src/ui/Features/Video/SpeechToText/OpenAiCompatible/OpenAiSttService.cs:107

        CancellationToken cancellationToken = default)
    {
        // Apply the per-call deadline via a linked CTS rather than the shared
        // HttpClient.Timeout, so the shared client stays unmodified.
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        if (_settings.TimeoutSeconds > 0)
        {
            timeoutCts.CancelAfter(TimeSpan.FromSeconds(_settings.TimeoutSeconds));
        }

        try
        {
            return await TranscribeCoreAsync(audioStream, fileName, language, progress, segmentProgress, timeoutCts.Token);
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            // Our own timeout fired, not a user cancel — surface it as an error
            // so the caller doesn't mistake it for cancellation.
            throw new TimeoutException($"STT request timed out after {_settings.TimeoutSeconds} seconds.");
        }
    }

    private async Task<OpenAiCompatibleSttResponse> TranscribeCoreAsync(
        Stream audioStream,
        string fileName,
        string? language,
        IProgress<string>? progress,
        IProgress<OpenAiCompatibleSegment>? segmentProgress,
        CancellationToken cancellationToken)
    {
        using var content = new MultipartFormDataContent();

        // Content-Type, upload filename extension, and bytes must all agree —
        // OpenAI rejects e.g. webm-Opus bytes sent as audio/wav. The on-disk
        // extension is the source of truth: the ViewModel's 16 kHz WAV short-
        // circuit can hand us a real .wav even when the user picked "mp3" in
        // settings. Fall back to the configured AudioFormat only when the

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Raise _settings.TimeoutSeconds in the engine settings.
  2. Verify endpoint reachability and latency with a direct curl/POST of the same audio.
  3. Reduce per-request audio size (chunk the audio) or enable streaming.
  4. Point at a closer/faster provider or a self-hosted model.

Example fix

// before: default short timeout
_settings.TimeoutSeconds = 30;
// after: sized to real upload + inference time for large files
_settings.TimeoutSeconds = 180;
Defensive patterns

Strategy: retry

Validate before calling

if (_settings.TimeoutSeconds < estimatedUploadPlusInferSeconds(audioStream))
    _settings.TimeoutSeconds = estimatedUploadPlusInferSeconds(audioStream);

Try / catch

try { return await service.TranscribeAsync(...); }
catch (TimeoutException ex) when (ex.Message.Contains("STT request timed out"))
{ /* increase TimeoutSeconds, then retry once */ }

Prevention

When it happens

Trigger: TranscribeCoreAsync (POST to EndpointUrl) runs longer than _settings.TimeoutSeconds while the caller's cancellationToken is still alive. The internal CTS is the one that cancelled.

Common situations: Slow/overloaded remote endpoint; large audio file uploaded with streaming disabled; endpoint behind a high-latency proxy; TimeoutSeconds configured too low in engine settings; network stall mid-upload.

Understand the failure class

Related errors


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