SubtitleEdit/subtitleedit · error · InvalidOperationException
Failed to download file after {maxRetries} attempts. URL: {u
Error message
Failed to download file after {maxRetries} attempts. URL: {url}. Downloaded: {bytesDownloaded}/{totalBytes ?? 0} bytes What it means
The terminal failure of DownloadHelper.DownloadFileAsync: every retry attempt failed and the loop exited. The message reports the URL, the configured maxRetries, bytes actually on disk, and the total expected (or 0 if unknown). The lastException is attached as InnerException so the root cause (timeout, socket error, truncation, etc.) is preserved. InvalidOperationException is the type to catch at call sites.
Source
Thrown at src/ui/Logic/Download/DownloadHelper.cs:189
var jitter = Random.Shared.Next(0, 1000);
var delayMs = baseDelay + jitter;
await Task.Delay(delayMs, CancellationToken.None).ConfigureAwait(false);
// Don't reset stream position - we'll resume from where we left off
// Only reset if we can't seek (which means we can't resume anyway)
if (!destination.CanSeek && destination.Position > 0)
{
throw new InvalidOperationException(
"Cannot retry download on non-seekable stream after partial download",
lastException);
}
}
}
// All retries exhausted
var bytesDownloaded = destination.CanSeek ? destination.Position : 0;
throw new InvalidOperationException(
$"Failed to download file after {maxRetries} attempts. URL: {url}. Downloaded: {bytesDownloaded}/{totalBytes ?? 0} bytes",
lastException);
}
private static async Task<bool> CheckRangeSupport(
HttpClient httpClient,
string url,
CancellationToken cancellationToken)
{
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(10));
using var request = new HttpRequestMessage(HttpMethod.Head, url);
using var response = await httpClient.SendAsync(request, cts.Token).ConfigureAwait(false);
return response.Headers.AcceptRanges?.Contains("bytes") == true;View on GitHub (pinned to 17a9f07487)
Solutions
- Inspect the InnerException of the thrown InvalidOperationException — it holds the actual cause of the final attempt.
- Verify connectivity to the URL host (curl/HEAD) and that no firewall/proxy is blocking large transfers.
- Increase maxRetries or timeoutSeconds if the cause is intermittent, and ensure a seekable destination so each retry resumes rather than restarts.
- For a permanent server-side issue, point the service at a mirror or wait for the upstream release to be republished.
Example fix
// before: default 5 retries, 30-min timeout
await DownloadHelper.DownloadFileAsync(http, url, fs, progress, ct);
// after: surface the root cause and allow more patience
try { await DownloadHelper.DownloadFileAsync(http, url, fs, progress, ct, maxRetries: 8, timeoutSeconds: 3600); }
catch (InvalidOperationException ex)
{
_logger.Error(ex, "Download failed permanently: {Root}", ex.InnerException?.Message);
throw;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight connectivity check before the long download using var probe = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, url), ct); probe.EnsureSuccessStatusCode();
Try / catch
try { await DownloadHelper.DownloadFileAsync(http, url, fs, progress, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Failed to download file after"))
{ _logger.Error(ex, "All retries exhausted. Root: {Root}", ex.InnerException?.Message); throw; } Prevention
- Always log InnerException — it carries the real cause of the final attempt.
- Increase maxRetries/timeoutSeconds for known-flaky environments.
- Pre-flight HEAD the URL and verify DNS/firewall before kicking off a 30-min download.
When it happens
Trigger: Persistent network failure across all attempts: repeated timeouts (timeoutSeconds=1800 default, 30 min), DNS errors, connection refused, server returning 5xx that EnsureSuccessStatusCode rejects, or repeated truncation on a non-resumable connection. Each attempt waited baseDelay = min(2000*2^(n-1), 30000) + up to 1000ms jitter.
Common situations: Offline / captive-portal network at download time; corporate firewall blocking the CDN host; server-side outage for the artifact; throttled connection where each attempt times out; clock/MTU misconfiguration causing consistent resets. Inspect lastException (InnerException chain) to pinpoint.
Related errors
- Dictionary download failed: {url}
- DashScope transcription timed out after {_settings.TimeoutSe
- STT request timed out after {_settings.TimeoutSeconds} secon
- OpenRouter transcription timed out after {_settings.TimeoutS
- crispasr (chatterbox) did not report healthy within 15 minut
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/b720aefbe3da3a3e.
Report an issue: GitHub.