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

  1. Bypass caches/service workers for the download URL (or hard-reload the request)
  2. If a custom fetch wrapper is injected, ensure it forwards the original Response untouched
  3. Retry the download — genuinely empty 2xx responses from the CDN are transient
  4. 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

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


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/492379217baf72b9. Report an issue: GitHub.