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

  1. Inspect the InnerException of the thrown InvalidOperationException — it holds the actual cause of the final attempt.
  2. Verify connectivity to the URL host (curl/HEAD) and that no firewall/proxy is blocking large transfers.
  3. Increase maxRetries or timeoutSeconds if the cause is intermittent, and ensure a seekable destination so each retry resumes rather than restarts.
  4. 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

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


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