{"record":{"id":"02c29f456c570d71","repo":"JuliusBrussee/caveman","slug":"binary-download-failed-response-body-missing","errorCode":null,"errorMessage":"binary download failed: response body missing","messagePattern":"binary download failed: response body missing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/binary-installer/installer.mjs","lineNumber":137,"sourceCode":"    if (!line) continue;\n    const match = line.match(/^([a-f0-9]{64})  ([A-Za-z0-9._-]+)$/);\n    if (!match) throw new Error(\"signed checksum manifest is malformed\");\n    if (match[2] === artifact) return match[1];\n  }\n  throw new Error(`signed checksum manifest does not contain ${artifact}`);\n}\n\nfunction cleanup(path) {\n  try {\n    unlinkSync(path);\n  } catch (error) {\n    if (error.code !== \"ENOENT\") throw error;\n  }\n}\n\nasync function download(url, part, timeout) {\n  const response = await asset(url, timeout);\n  if (!response.body) throw new Error(\"binary download failed: response body missing\");\n  const hash = createHash(\"sha256\");\n  const file = await open(part, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);\n  const reader = response.body.getReader();\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      hash.update(value);\n      await file.write(value);\n    }\n  } finally {\n    reader.releaseLock();\n    await file.close();\n  }\n  return hash.digest(\"hex\");\n}\n\n// Windows refuses to replace a file another process holds open, and the","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/binary-installer/installer.mjs#L119-L155","documentation":"`download(url, part, timeout)` first obtains a response via `asset()` (already status-checked), then requires a streaming body to hash and write to the .part file. If `response.body` is null/undefined, the response cannot be streamed, so it throws immediately before opening the temp file. Defensive-undeclared-stream guard against runtimes/proxies that swallow the body.","triggerScenarios":"`download()` called (from `ensureBinary`) where the fetch response from `asset()` has a null body — e.g. a stubbed/mocked global fetch in tests, an HTTP/0.9 or empty-bodied 204-style response from a misbehaving middlebox, or an undici version where body is consumed/null for certain responses.","commonSituations":"Test environments where fetch is mocked without a `body` ReadableStream; corporate middleboxes that terminate streaming responses; exotic runtimes (older Node with different fetch polyfill) lacking a spec-compliant body stream.","solutions":["Verify you're on a Node version with a real undici-based fetch (Node >= 18) so responses carry a ReadableStream body.","Check whether global fetch is mocked in your process/test setup and restore the real fetch for the installer.","curl the artifact URL to confirm the server streams bytes; if a proxy strips bodies, bypass it.","Install the binary out-of-band and set the envVar so download() is never invoked."],"exampleFix":"// before: mocked fetch without body\nglobal.fetch = async () => ({ ok: true, status: 200, text: async () => \"\" });\n// after: provide a body stream\nglobal.fetch = async () => ({ ok: true, status: 200, body: new Blob([data]).stream() });","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasBody(res) { return res != null && typeof res.body?.getReader === \"function\"; }","tryCatchPattern":"try {\n  await ensureBinary({ name, envVar });\n} catch (e) {\n  if (String(e.message).includes(\"response body missing\")) {\n    console.error(\"fetch response had no stream — check fetch mocks / runtime\");\n  }\n  throw e;\n}","preventionTips":["Don't mock global.fetch in processes that install binaries; mock at a lower level with real streams.","Use Node >= 18 with built-in undici fetch.","Bypass proxies known to strip or buffer streaming responses.","Prefer providing the binary via envVar in test environments."],"tags":["network","download","stream"],"backgroundTag":"empty-response-body","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}