LykosAI/StabilityMatrix · error · ApplicationException

Response is null

Error message

Response is null

What it means

ResumeDownloadToFileAsync resolves the download URL (including redirect targets), then loops with a small retry/backoff to obtain a response that reports a usable Content-Length for resuming a Range download. response stays null only when every one of the 4 retry attempts left remainingContentLength == 0 (i.e. the server never reported a content length, e.g. an empty 206/200 body), so this ApplicationException signals 'could not determine remaining content length after retries' — effectively a server not cooperating with resumable downloads.

Solutions

  1. Retry the download later or against a different mirror URL — the error usually means the host does not supply Content-Length for ranged responses.
  2. Verify the server supports Range requests (look for Accept-Ranges: bytes in the headers); if not, download the file from scratch instead of resuming.
  3. Check for proxies/VPNs stripping Content-Length and bypass them.
  4. Pass a named httpClientName with appropriate configuration (timeout, headers) for the specific host.
  5. If the partial file is already complete (existingFileSize == full size), skip ResumeDownloadToFileAsync entirely.

Example fix

// before
await downloadService.ResumeDownloadToFileAsync(url, path, existingSize);
// after
try
{
    await downloadService.ResumeDownloadToFileAsync(url, path, existingSize);
}
catch (ApplicationException ex) when (ex.Message == "Response is null")
{
    // Server never returned a usable content length after retries;
    // fall back to a fresh (non-resume) download.
    File.Delete(path);
    await downloadService.DownloadToFileAsync(url, path);
}
Defensive patterns

Strategy: fallback

Validate before calling

// Check the host supports ranged downloads before resuming
using var head = new HttpRequestMessage(HttpMethod.Head, downloadUrl);
using var resp = await httpClient.SendAsync(head);
bool resumable = resp.Headers.AcceptRanges?.Contains("bytes") == true
    && resp.Content.Headers.ContentLength > existingFileSize;
if (!resumable)
{
    File.Delete(downloadPath); // start fresh instead of resuming
}

Type guard

bool HasContentLength(HttpResponseMessage? r) =>
    r?.Content?.Headers?.ContentLength is long len and > 0;

Try / catch

try
{
    await downloadService.ResumeDownloadToFileAsync(url, path, existingSize, progress, httpClientName, ct);
}
catch (ApplicationException ex) when (ex.Message == "Response is null")
{
    logger.LogWarning(ex, "Resume failed: no content-length after retries; restarting download");
    File.Delete(path);
    await downloadService.DownloadToFileAsync(url, path, progress, httpClientName, ct);
}

Prevention

When it happens

Trigger: Calling ResumeDownloadToFileAsync against a server whose ranged GET responses repeatedly lack a Content-Length header: the retry loop (4 attempts, 50ms decorrelated jitter backoff) exits without ever setting remainingContentLength > 0, so response is still the initial null when the guard at line 284 runs.

Common situations: Download hosts (CDNs, archives, mirrors) that don't honor Range requests or omit Content-Length on 206 responses; misconfigured proxies stripping headers; resuming a download that is actually already complete (remaining length 0) from a server that keeps returning indeterminate responses; transient server-side failures during all 4 quick retries.

Related errors


AI-assisted analysis of LykosAI/StabilityMatrix@af93d6ef57 (2026-09-12). Data as JSON: /api/errors/f30d6170ff338a29. Report an issue: GitHub.

Appendix: source

Thrown at StabilityMatrix.Core/Services/DownloadService.cs:286

                file.Seek(0, SeekOrigin.Begin);
                file.SetLength(0);
                existingFileSize = 0;
            }

            originalContentLength =
                response.Content.Headers.ContentRange?.Length.GetValueOrDefault()
                ?? (existingFileSize + remainingContentLength);

            if (remainingContentLength > 0)
                break;

            logger.LogDebug("Retrying get-headers for content-length");
            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
        }

        if (response == null)
        {
            throw new ApplicationException("Response is null");
        }

        var isIndeterminate = remainingContentLength == 0;

        await using var stream = await response
            .Content.ReadAsStreamAsync(cancellationToken)
            .ConfigureAwait(false);
        var totalBytesRead = 0L;
        var stopwatch = Stopwatch.StartNew();
        var buffer = new byte[BufferSize];
        while (true)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
            if (bytesRead == 0)
                break;
            await file.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);

View on GitHub (pinned to af93d6ef57)