JuliusBrussee/caveman · error · Error
binary download failed: response body missing
Error message
binary download failed: response body missing
What it means
`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.
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.
Example fix
// before: mocked fetch without body
global.fetch = async () => ({ ok: true, status: 200, text: async () => "" });
// after: provide a body stream
global.fetch = async () => ({ ok: true, status: 200, body: new Blob([data]).stream() }); Defensive patterns
Strategy: type-guard
Type guard
function hasBody(res) { return res != null && typeof res.body?.getReader === "function"; } Try / catch
try {
await ensureBinary({ name, envVar });
} catch (e) {
if (String(e.message).includes("response body missing")) {
console.error("fetch response had no stream — check fetch mocks / runtime");
}
throw e;
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/02c29f456c570d71.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/binary-installer/installer.mjs:137
if (!line) continue;
const match = line.match(/^([a-f0-9]{64}) ([A-Za-z0-9._-]+)$/);
if (!match) throw new Error("signed checksum manifest is malformed");
if (match[2] === artifact) return match[1];
}
throw new Error(`signed checksum manifest does not contain ${artifact}`);
}
function cleanup(path) {
try {
unlinkSync(path);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
async function download(url, part, timeout) {
const response = await asset(url, timeout);
if (!response.body) throw new Error("binary download failed: response body missing");
const hash = createHash("sha256");
const file = await open(part, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
const reader = response.body.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
hash.update(value);
await file.write(value);
}
} finally {
reader.releaseLock();
await file.close();
}
return hash.digest("hex");
}
// Windows refuses to replace a file another process holds open, and theView on GitHub (pinned to 3ee70a1026)