aaif-goose/goose · error
Response body is null
Error message
Response body is null
What it means
After a 2xx download response, the updater requires response.body (a ReadableStream) to stream the artifact chunk-by-chunk with progress. A null body means the fetch implementation returned headers without a stream — possible with responses constructed from cache/service workers, HTTP 204/HEAD-like empties, or environments whose fetch lacks streaming. The download cannot proceed and throws before reading any bytes.
Source
Thrown at ui/desktop/src/utils/githubUpdater.ts:192
const response = await fetch(downloadUrl);
const fetchDuration = Date.now() - downloadStartTime;
log.info(
`GitHubUpdater: Download response received in ${fetchDuration}ms - Status: ${response.status} ${response.statusText}`
);
if (!response.ok) {
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
}
// Get total size from headers
const contentLength = response.headers.get('content-length');
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
log.info(
`GitHubUpdater: Content-Length: ${totalSize} bytes (${(totalSize / 1024 / 1024).toFixed(2)} MB)`
);
if (!response.body) {
throw new Error('Response body is null');
}
let lastReportedPercent = -1; // Track last reported percentage to throttle updates
let lastLoggedPercent = -1; // Track for logging at 10% intervals
// Read the response stream
log.info('GitHubUpdater: Starting to read response stream...');
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
let downloadedSize = 0;
let lastProgressTime = Date.now();
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
downloadedSize += value.length;
View on GitHub (pinned to 3810898a74)
Solutions
- Bypass caches/service workers for the download URL (or hard-reload the request)
- If a custom fetch wrapper is injected, ensure it forwards the original Response untouched
- Retry the download — genuinely empty 2xx responses from the CDN are transient
- Fall back to opening the release page and downloading manually if streaming keeps failing
Defensive patterns
Strategy: retry
Validate before calling
const response = await fetch(downloadUrl);
if (response.ok && !response.body) {
// bypass cache or refetch with cache: 'reload'
response = await fetch(downloadUrl, { cache: 'reload' });
} Type guard
const hasStreamBody = (r: Response): boolean => r.body != null;
Try / catch
try {
await downloadArtifact(url);
} catch (e) {
if (String(e).includes('Response body is null')) {
await downloadArtifact(url, { cache: 'reload' }); // one retry, then surface
} else throw e;
} Prevention
- Pass cache: 'no-store' for large artifact downloads
- Avoid fetch wrappers that consume/clash with the body stream
- Check response.body before entering the progress loop
When it happens
Trigger: downloadUpdate() receiving a cached or synthesized Response whose body is null; a fetch polyfill (or Electron net.fetch in odd modes) that doesn't populate body; a 204 No Content from a misconfigured redirect chain.
Common situations: Service-worker caches intercepting the CDN request; custom fetch wrappers that clone()/consume the body earlier; rare proxy responses that terminate without a body yet claim success.
Related errors
- Download failed: ${response.status} ${response.statusText}
- Download failed after {} retries due to stream interruption
- HTTP error! status: ${response.status}
- GitHub API returned ${response.status}: ${response.statusTex
- Download failed after {} retries: {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/492379217baf72b9.
Report an issue: GitHub.