{"record":{"id":"c9f94924d6aa064f","repo":"heygen-com/hyperframes","slug":"truncated-download-got-bytes-bytes-expected","errorCode":null,"errorMessage":"Truncated download: got ${bytes} bytes, expected ${totalOpt} (content-length). The presigned URL may have expired mid-transfer — refetch via `hyperframes cloud get`.","messagePattern":"Truncated download: got (.+?) bytes, expected (.+?) \\(content-length\\)\\. The presigned URL may have expired mid-transfer — refetch via `hyperframes cloud get`\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/cloud/download.ts","lineNumber":76,"sourceCode":"\n  const file = createWriteStream(destPath);\n  let bytes = 0;\n  let errored = false;\n  try {\n    for await (const chunk of res.body as unknown as AsyncIterable<Uint8Array>) {\n      if (options.signal?.aborted) {\n        throw options.signal.reason instanceof Error\n          ? options.signal.reason\n          : new Error(\"Download aborted\");\n      }\n      bytes += chunk.byteLength;\n      options.onProgress?.(bytes, totalOpt);\n      if (!file.write(chunk)) {\n        await waitForDrain(file, options.signal);\n      }\n    }\n    if (totalOpt !== undefined && bytes !== totalOpt) {\n      throw new Error(\n        `Truncated download: got ${bytes} bytes, expected ${totalOpt} (content-length). ` +\n          `The presigned URL may have expired mid-transfer — refetch via \\`hyperframes cloud get\\`.`,\n      );\n    }\n  } catch (err) {\n    errored = true;\n    throw err;\n  } finally {\n    await closeFile(file);\n    if (errored) {\n      // Don't let a partial file pose as the final artifact. Best-\n      // effort unlink — if it fails (already gone, permission), we\n      // re-throw the original error.\n      try {\n        unlinkSync(destPath);\n      } catch {\n        /* swallow */\n      }","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/cloud/download.ts#L58-L94","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"let lastErr: unknown;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    const url = await client.getAssetUrl(assetId); // fresh each attempt\n    await downloadToFile(url, dest, { signal });\n    return;\n  } catch (err) {\n    lastErr = err;\n    if (!/Truncated download/.test(String(err?.message))) throw err;\n  }\n}\nthrow lastErr;","handlingStrategy":"retry","validationCode":"// Cannot fully prevent mid-stream truncation, but bound the window:\n// reserve the URL right before download and keep total duration < TTL.","typeGuard":"function isPollTimeoutLike(err: unknown): boolean {\n  return err instanceof Error && /Truncated download/.test(err.message);\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    const url = await client.getAssetUrl(id);\n    await downloadToFile(url, dest);\n    break;\n  } catch (err) {\n    if (!/Truncated download/.test(String(err?.message)) || attempt === 2) throw err;\n  }\n}","preventionTips":["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."],"tags":["network","download","cloud","data-integrity","presigned-url"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}