JuliusBrussee/caveman · error · Error

binary download failed: response body missing

Error message

binary download failed: response body missing

What it means

download() in the binary installer requires response.body (a web ReadableStream) before streaming the artifact to disk and hashing it. A response that is ok yet has a falsy body cannot be written or verified, so it fails before creating the .part file.

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");
}

export async function ensureBinary({ name, envVar }) {

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Retry once — genuinely transient empty 200 responses exist behind flaky proxies
  2. If a custom fetch or interceptor is installed globally, disable it for the install step or make it return real Response objects
  3. Verify with curl -i that the artifact URL really returns a body; if not, treat it as a server or mirror problem (HTTP-status failure)

Example fix

# before (custom global fetch mock in the process)
globalThis.fetch = async () => ({ ok: true });   // no body → error

# after (during install)
globalThis.fetch = undici.fetch;   # or drop the shim before running setup
Defensive patterns

Strategy: retry

Validate before calling

// guard if you inject a custom fetch into the installer's process
if (typeof globalThis.fetch === "function") {
  const probe = await globalThis.fetch("data:text/plain,1");
  if (!probe.body && typeof probe.arrayBuffer !== "function") {
    throw new Error("fetch shim lacks body — disable it for setup");
  }
}

Type guard

function hasBody(response) {
  return response.body != null && typeof response.body.getReader === "function";
}

Try / catch

try { await ensureBinary({ name, envVar }); }
catch (e) {
  if (/response body missing/.test(String(e?.message))) {
    await delay(1000);
    return ensureBinary({ name, envVar });
  }
  throw e;
}

Prevention

When it happens

Trigger: Interceptors or test doubles returning Response-like objects without a body; fetch implementations (older polyfills, some edge runtimes) that expose arrayBuffer() but a null body; a proxy returning an empty 200.

Common situations: Running the installer under a custom global fetch shim; unit tests mocking fetch with literal { ok: true } objects; unusual runtimes where Node's undici has been replaced.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/02c29f456c570d71. Report an issue: GitHub.