heygen-com/hyperframes · error
Failed to download ${url}: empty response body
Error message
Failed to download ${url}: empty response body What it means
Thrown by downloadToFile when the response is res.ok (2xx) but res.body is null/undefined. This is an unusual server behavior — a success status with no body — that the client refuses to silently turn into a zero-byte file. Like the HTTP-error branch it fires before destPath is created, so no partial artifact appears.
Source
Thrown at packages/cli/src/cloud/download.ts:50
/**
* Stream `url` into `destPath`. Creates the parent directory if needed,
* truncates any existing file at the destination, and deletes the
* partial output on any error so the caller never observes a corrupt
* file at the returned path.
*/
// fallow-ignore-next-line complexity
export async function downloadToFile(
url: string,
destPath: string,
options: DownloadOptions = {},
): Promise<DownloadResult> {
const fetchImpl = options.fetchImpl ?? fetch;
const res = await fetchImpl(url, { signal: options.signal });
if (!res.ok) {
throw new Error(`Failed to download ${url}: HTTP ${res.status} ${res.statusText}`);
}
if (!res.body) {
throw new Error(`Failed to download ${url}: empty response body`);
}
mkdirSync(dirname(destPath), { recursive: true });
const totalHeader = res.headers.get("content-length");
const total = totalHeader ? Number.parseInt(totalHeader, 10) : undefined;
const totalOpt = total !== undefined && Number.isFinite(total) ? total : undefined;
const file = createWriteStream(destPath);
let bytes = 0;
let errored = false;
try {
for await (const chunk of res.body as unknown as AsyncIterable<Uint8Array>) {
if (options.signal?.aborted) {
throw options.signal.reason instanceof Error
? options.signal.reason
: new Error("Download aborted");
}View on GitHub (pinned to c2996c8626)
Solutions
- Verify the URL actually points at a non-empty object (curl -i and check Content-Length).
- If the object genuinely is empty, handle the empty case explicitly upstream rather than relying on downloadToFile.
- For test doubles, ensure the mocked Response includes a non-empty ReadableStream body.
- Re-reserve the asset URL if the store returned a degenerate response for a known-nonempty object.
Example fix
// before: test mock returns ok but no body
fetchMock.mockResponse('', { status: 200 });
// after: include a body stream
fetchMock.mockResponse(Buffer.from(bytes), { status: 200 }); Defensive patterns
Strategy: validation
Validate before calling
async function assertNonEmptyBody(url: string): Promise<void> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
if (!res.body) throw new Error('server returned 2xx with no body — empty or misconfigured object');
} Try / catch
try {
await downloadToFile(url, dest);
} catch (err) {
if (/empty response body/.test(String(err?.message))) {
// treat as a degenerate object; re-reserve or skip
} else throw err;
} Prevention
- For test mocks, always include a ReadableStream body on the Response.
- Verify the object is non-empty before issuing a presigned download.
- Treat a 2xx-with-no-body as a server/store defect worth reporting.
When it happens
Trigger: A server returns 200 with Content-Length: 0 and no body, or a fetch implementation/runtime where the stream is absent (some non-browser runtimes, mocked fetches in tests, or a HEAD-shaped response to a GET).
Common situations: A misconfigured object store or CDN returning an empty 200 for a zero-size object; a test double that returns { ok: true } without a body; a streaming transport that closed the connection before emitting any chunk.
Related errors
- Failed to download ${url}: HTTP ${res.status} ${res.statusTe
- Truncated download: got ${bytes} bytes, expected ${totalOpt}
- Model download failed: ${model}
- Invalid JSON response: ${(err as Error).message}
- figma render download failed: HTTP ${res.status}
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/8d61fd3d0e4355dc.
Report an issue: GitHub.