SubtitleEdit/subtitleedit · error · InvalidOperationException

Dictionary download failed: {url}

Error message

Dictionary download failed: {url}

What it means

Thrown after a spell-check dictionary download via DownloadDictionary completes without throwing, but the resulting MemoryStream has zero bytes. The download service (SpellCheckDictionaryDownloadService) delegates to DownloadHelper.DownloadFileAsync, which can succeed with an empty body if the server returns HTTP 200 with no content or a redirect to an empty page. This guard catches the silent-empty-download case that would otherwise produce a corrupt dictionary file.

Source

Thrown at src/ui/Features/SpellCheck/GetDictionaries/GetDictionariesViewModel.cs:206

        if (!Directory.Exists(folder))
        {
            Directory.CreateDirectory(folder);
        }

        var dicFiles = new List<string>();

        for (var i = 0; i < files.Count; i++)
        {
            var url = files[i];
            var fileIndex = i;
            var fileProgress = new Progress<float>(p => progress.Report((fileIndex + p) / files.Count));

            using var stream = new MemoryStream();
            await _spellCheckDictionaryDownloadService.DownloadDictionary(stream, url, fileProgress, cancellationToken);

            if (stream.Length == 0)
            {
                throw new InvalidOperationException($"Dictionary download failed: {url}");
            }

            stream.Position = 0;

            if (IsHunspellFile(url))
            {
                var targetFileName = Path.Combine(folder, GetFileNameFromUrl(url));
                await using (var fileStream = File.Create(targetFileName))
                {
                    await stream.CopyToAsync(fileStream, cancellationToken);
                }

                if (targetFileName.EndsWith(".dic", StringComparison.OrdinalIgnoreCase))
                {
                    dicFiles.Add(targetFileName);
                }
            }
            else

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Verify the failing URL in a browser or with curl -I to check the actual Content-Length and status code.
  2. Check the _spellCheckDictionaryDownloadService's HttpClient configuration for proxy settings or redirect behaviour.
  3. Confirm the dictionary URL list is current — if the host moved files, update the URL source.
  4. If behind a corporate proxy, ensure the proxy is not stripping response bodies for the dictionary host.
  5. Add logging inside DownloadHelper.DownloadFileAsync to capture the HTTP status code and Content-Length header for diagnosis.

Example fix

// before
using var stream = new MemoryStream();
await _spellCheckDictionaryDownloadService.DownloadDictionary(stream, url, fileProgress, cancellationToken);
if (stream.Length == 0)
{
    throw new InvalidOperationException($"Dictionary download failed: {url}");
}

// after — include the HTTP status / content-length in the message for diagnosis
using var stream = new MemoryStream();
await _spellCheckDictionaryDownloadService.DownloadDictionary(stream, url, fileProgress, cancellationToken);
if (stream.Length == 0)
{
    throw new InvalidOperationException(
        $"Dictionary download produced 0 bytes from {url}. The URL may be stale, blocked by a proxy, or the server returned an empty response.");
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate URL reachability and headers before downloading
using var probeReq = new HttpRequestMessage(HttpMethod.Head, url);
using var probeResp = await httpClient.SendAsync(probeReq, cancellationToken);
if (!probeResp.IsSuccessStatusCode || probeResp.Content.Headers.ContentLength is 0)
{
    // Skip or flag this dictionary URL before attempting the full download
    continue;
}

Try / catch

try { await downloadService.DownloadDictionary(stream, url, progress, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Dictionary download failed"))
{ /* log url, skip this dictionary, continue with remaining files */ }

Prevention

When it happens

Trigger: The URL in the files list returns HTTP 200 with an empty response body, a 30x redirect chain lands on an empty page, a CDN edge serves a stale/empty object, or a proxy strips the body. The check fires only when stream.Length == 0 after a successful (non-throwing) DownloadDictionary call.

Common situations: Dictionary URL list is outdated and the host has removed the file but returns 200 with an HTML error page that DownloadHelper writes as empty; corporate proxy intercepts and returns an empty body; the URL is an HTTPS endpoint whose certificate changed and a middlebox returns an empty response; CDN misconfiguration serves a zero-length object.

Related errors


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