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 theView on GitHub (pinned to 17a9f07487)
Solutions
- Raise _settings.TimeoutSeconds in the engine settings.
- Verify endpoint reachability and latency with a direct curl/POST of the same audio.
- Reduce per-request audio size (chunk the audio) or enable streaming.
- 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
- Size TimeoutSeconds to upload bandwidth + model inference time, not a generic default.
- Chunk large audio so each request finishes well under the timeout.
- Distinguish this TimeoutException from the caller's cancellation before retrying.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- DashScope transcription timed out after {_settings.TimeoutSe
- STT request failed with status {statusCode} ({response.Statu
- OpenRouter transcription timed out after {_settings.TimeoutS
- DashScope upload-policy request failed ({(int)policyResponse
- DashScope OSS upload failed ({(int)uploadResponse.StatusCode
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/23df821b75422af6.
Report an issue: GitHub.