{"record":{"id":"1f79cb76fd280395","repo":"SubtitleEdit/subtitleedit","slug":"dashscope-transcription-timed-out-after-settings","errorCode":null,"errorMessage":"DashScope transcription timed out after {_settings.TimeoutSeconds} seconds.","messagePattern":"DashScope transcription timed out after (.+?) seconds\\.","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/SpeechToText/DashScope/DashScopeSttService.cs","lineNumber":102,"sourceCode":"            progress?.Report(\"Submitting transcription task...\");\n            _settings.Logger?.Invoke($\"DashScope: submitting async task for {ossUrl}\");\n            var taskId = await SubmitTaskAsync(ossUrl, language, ct);\n\n            progress?.Report(\"Waiting for transcription to complete...\");\n            var transcriptionUrl = await PollTaskAsync(taskId, ct);\n\n            _settings.Logger?.Invoke($\"DashScope: fetching result from {transcriptionUrl}\");\n            using var resultResponse = await _httpClient.GetAsync(transcriptionUrl, HttpCompletionOption.ResponseHeadersRead, ct);\n            resultResponse.EnsureSuccessStatusCode();\n            var resultJson = await resultResponse.Content.ReadAsStringAsync(ct);\n\n            return ParseTranscriptionResult(resultJson);\n        }\n        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)\n        {\n            // Our own timeout fired, not a user cancel — surface it as an error\n            // so the caller doesn't mistake it for cancellation.\n            throw new TimeoutException($\"DashScope transcription timed out after {_settings.TimeoutSeconds} seconds.\");\n        }\n    }\n\n    /// <summary>\n    /// Upload the audio to DashScope temporary storage and return its\n    /// <c>oss://</c> URL. Two steps: fetch an upload policy, then POST the file\n    /// to the returned OSS host with the signed form fields (file field last).\n    /// </summary>\n    private async Task<string> UploadFileAsync(byte[] audioBytes, string fileName, CancellationToken ct)\n    {\n        var model = string.IsNullOrWhiteSpace(_settings.Model) ? \"qwen3-asr-flash-filetrans\" : _settings.Model;\n        var policyUrl = $\"{BaseUrl}{UploadsPath}?action=getPolicy&model={Uri.EscapeDataString(model)}\";\n        using var policyRequest = new HttpRequestMessage(HttpMethod.Get, policyUrl);\n        policyRequest.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _settings.ApiKey);\n\n        using var policyResponse = await _httpClient.SendAsync(policyRequest, HttpCompletionOption.ResponseHeadersRead, ct);\n        if (!policyResponse.IsSuccessStatusCode)\n        {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/SpeechToText/DashScope/DashScopeSttService.cs#L84-L120","documentation":"Thrown when the DashScope speech-to-text pipeline's internal timeout (TimeoutSeconds setting) fires via a linked CancellationTokenSource, distinct from a user-initiated cancel. The catch guard `when (!cancellationToken.IsCancellationRequested)` ensures only the timeout CTS (not user cancel) triggers this TimeoutException. The pipeline includes upload, submit, and polling steps, any of which can exceed the timeout.","triggerScenarios":"TimeoutSeconds > 0 is set and the linked CTS cancels during UploadFileAsync, SubmitTaskAsync, PollTaskAsync, or the result download — while the user's own cancellationToken has not been cancelled. Common during long audio files where the polling loop (3-second intervals) runs for minutes.","commonSituations":"Transcribing a long audio file (30+ minutes) with a low TimeoutSeconds setting; DashScope's task queue is slow and polling takes longer than expected; the upload step is slow on a constrained network; TimeoutSeconds was left at a default that's too low for production audio lengths.","solutions":["Increase _settings.TimeoutSeconds to accommodate the audio length (rough rule: 1-2 minutes per 10 minutes of audio for the polling phase).","Set TimeoutSeconds to 0 to disable the internal timeout entirely and rely only on user cancellation.","Pre-split long audio files into shorter segments before submission.","Check DashScope's status page for queue delays if timeouts are intermittent.","Verify network bandwidth is sufficient for the upload step (audio is uploaded in full before processing begins)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Scale the timeout to audio duration before starting\nvar audioDurationSeconds = GetAudioDuration(audioFilePath);\nvar minTimeout = (int)(audioDurationSeconds * 0.5) + 120; // 50% of audio + 2 min overhead\nif (_settings.TimeoutSeconds > 0 && _settings.TimeoutSeconds < minTimeout)\n    _settings = _settings with { TimeoutSeconds = minTimeout };","typeGuard":null,"tryCatchPattern":"try { return await service.TranscribeAsync(audioPath, language, progress, segProgress, ct); }\ncatch (TimeoutException ex) when (ex.Message.Contains(\"DashScope\"))\n{ /* increase TimeoutSeconds, re-split audio, or retry */ }","preventionTips":["Set TimeoutSeconds proportional to the audio file duration (at least 1 minute per 10 minutes of audio).","Set TimeoutSeconds to 0 to disable internal timeouts for very long files.","Pre-split long audio into shorter segments before transcription.","Monitor DashScope's processing time for typical audio lengths and adjust the setting accordingly."],"tags":["network","timeout","dashscope","speech-to-text","cancellation"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}