heygen-com/hyperframes · error
Truncated download: got ${bytes} bytes, expected ${totalOpt}
Error message
Truncated download: got ${bytes} bytes, expected ${totalOpt} (content-length). The presigned URL may have expired mid-transfer — refetch via `hyperframes cloud get`. What it means
Thrown after the download stream completes when the accumulated byte count does not equal the content-length the server advertised. The message points at the most common cause — a presigned URL expiring mid-transfer — and the finally block unlinks the partial file so a corrupt artifact can never be observed at destPath. totalOpt is only set when content-length was present and finite, so responses without it cannot trip this check.
Source
Thrown at packages/cli/src/cloud/download.ts:76
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");
}
bytes += chunk.byteLength;
options.onProgress?.(bytes, totalOpt);
if (!file.write(chunk)) {
await waitForDrain(file, options.signal);
}
}
if (totalOpt !== undefined && bytes !== totalOpt) {
throw new Error(
`Truncated download: got ${bytes} bytes, expected ${totalOpt} (content-length). ` +
`The presigned URL may have expired mid-transfer — refetch via \`hyperframes cloud get\`.`,
);
}
} catch (err) {
errored = true;
throw err;
} finally {
await closeFile(file);
if (errored) {
// Don't let a partial file pose as the final artifact. Best-
// effort unlink — if it fails (already gone, permission), we
// re-throw the original error.
try {
unlinkSync(destPath);
} catch {
/* swallow */
}View on GitHub (pinned to c2996c8626)
Solutions
- Refetch a fresh presigned URL via `hyperframes cloud get` and retry the download from scratch.
- For very large assets, request shorter-TTL-bounded chunks or raise the presigned-URL lifetime with the API owner.
- Stabilize the network (wired CI runner, larger TCP buffers) if the cause is a mid-stream drop rather than expiry.
- Wrap downloadToFile in a bounded retry loop keyed on the 'Truncated download' message.
Example fix
let lastErr: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
try {
const url = await client.getAssetUrl(assetId); // fresh each attempt
await downloadToFile(url, dest, { signal });
return;
} catch (err) {
lastErr = err;
if (!/Truncated download/.test(String(err?.message))) throw err;
}
}
throw lastErr; Defensive patterns
Strategy: retry
Validate before calling
// Cannot fully prevent mid-stream truncation, but bound the window: // reserve the URL right before download and keep total duration < TTL.
Type guard
function isPollTimeoutLike(err: unknown): boolean {
return err instanceof Error && /Truncated download/.test(err.message);
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try {
const url = await client.getAssetUrl(id);
await downloadToFile(url, dest);
break;
} catch (err) {
if (!/Truncated download/.test(String(err?.message)) || attempt === 2) throw err;
}
} Prevention
- Reserve a fresh presigned URL immediately before each download attempt.
- For very large assets, raise the URL TTL or use multipart/chunked download.
- Wrap the call in a bounded retry keyed on the 'Truncated download' message.
When it happens
Trigger: A presigned URL expires while bytes are still flowing (S3 returns 403 mid-stream or truncates), a network drop closes the socket early without an error event, or a proxy enforces a smaller max-body-size than content-length and silently truncates.
Common situations: Large multi-hundred-MB uploads/downloads that outlast the presigned-URL TTL; flaky mobile/CI networks; a CDN with a response size cap; downloads started near the end of the URL's validity window.
Related errors
- Failed to download ${url}: HTTP ${res.status} ${res.statusTe
- Failed to download ${url}: empty response body
- Model download failed: ${model}
- Invalid JSON response: ${(err as Error).message}
- Direct upload PUT failed: ${res.status} ${res.statusText}${d
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/c9f94924d6aa064f.
Report an issue: GitHub.