{"record":{"id":"f30d6170ff338a29","repo":"LykosAI/StabilityMatrix","slug":"response-is-null","errorCode":null,"errorMessage":"Response is null","messagePattern":"Response is null","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"error","filePath":"StabilityMatrix.Core/Services/DownloadService.cs","lineNumber":286,"sourceCode":"                file.Seek(0, SeekOrigin.Begin);\n                file.SetLength(0);\n                existingFileSize = 0;\n            }\n\n            originalContentLength =\n                response.Content.Headers.ContentRange?.Length.GetValueOrDefault()\n                ?? (existingFileSize + remainingContentLength);\n\n            if (remainingContentLength > 0)\n                break;\n\n            logger.LogDebug(\"Retrying get-headers for content-length\");\n            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);\n        }\n\n        if (response == null)\n        {\n            throw new ApplicationException(\"Response is null\");\n        }\n\n        var isIndeterminate = remainingContentLength == 0;\n\n        await using var stream = await response\n            .Content.ReadAsStreamAsync(cancellationToken)\n            .ConfigureAwait(false);\n        var totalBytesRead = 0L;\n        var stopwatch = Stopwatch.StartNew();\n        var buffer = new byte[BufferSize];\n        while (true)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);\n            if (bytesRead == 0)\n                break;\n            await file.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/LykosAI/StabilityMatrix/blob/af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002/StabilityMatrix.Core/Services/DownloadService.cs#L268-L304","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the download later or against a different mirror URL — the error usually means the host does not supply Content-Length for ranged responses.","Verify the server supports Range requests (look for Accept-Ranges: bytes in the headers); if not, download the file from scratch instead of resuming.","Check for proxies/VPNs stripping Content-Length and bypass them.","Pass a named httpClientName with appropriate configuration (timeout, headers) for the specific host.","If the partial file is already complete (existingFileSize == full size), skip ResumeDownloadToFileAsync entirely."],"exampleFix":"// before\nawait downloadService.ResumeDownloadToFileAsync(url, path, existingSize);\n// after\ntry\n{\n    await downloadService.ResumeDownloadToFileAsync(url, path, existingSize);\n}\ncatch (ApplicationException ex) when (ex.Message == \"Response is null\")\n{\n    // Server never returned a usable content length after retries;\n    // fall back to a fresh (non-resume) download.\n    File.Delete(path);\n    await downloadService.DownloadToFileAsync(url, path);\n}","handlingStrategy":"fallback","validationCode":"// Check the host supports ranged downloads before resuming\nusing var head = new HttpRequestMessage(HttpMethod.Head, downloadUrl);\nusing var resp = await httpClient.SendAsync(head);\nbool resumable = resp.Headers.AcceptRanges?.Contains(\"bytes\") == true\n    && resp.Content.Headers.ContentLength > existingFileSize;\nif (!resumable)\n{\n    File.Delete(downloadPath); // start fresh instead of resuming\n}","typeGuard":"bool HasContentLength(HttpResponseMessage? r) =>\n    r?.Content?.Headers?.ContentLength is long len and > 0;","tryCatchPattern":"try\n{\n    await downloadService.ResumeDownloadToFileAsync(url, path, existingSize, progress, httpClientName, ct);\n}\ncatch (ApplicationException ex) when (ex.Message == \"Response is null\")\n{\n    logger.LogWarning(ex, \"Resume failed: no content-length after retries; restarting download\");\n    File.Delete(path);\n    await downloadService.DownloadToFileAsync(url, path, progress, httpClientName, ct);\n}","preventionTips":["Confirm the host sends Accept-Ranges: bytes and Content-Length before resuming","Avoid resuming through proxies/VPNs that strip response headers","If the partial file is already fully sized, skip resume and verify the file","Add a fallback to a full (non-resume) download wherever resume is used"],"tags":["network","download","http","retry","resume"],"backgroundTag":"unexpected-response-shape","analyzedSha":"af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002","analyzedAt":"2026-09-12T19:02:43.389Z","contentChangedAt":"2026-09-12T19:02:43.389Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}