heygen-com/hyperframes · error · Error

[build-zip] unzipped bundle ${formatBytes(unzippedBytes)} ex

Error message

[build-zip] unzipped bundle ${formatBytes(unzippedBytes)} exceeds limit ${formatBytes(opts.maxUnzippedBytes)} (Lambda ZIP ceiling: 250 MiB unzipped). Switch --source to the lighter option, or move Chrome to a Lambda Layer.

What it means

AWS Lambda enforces a 250 MiB unzipped deployment package ceiling. The build computes the staging directory size via 'du -sb' before zipping and throws if it exceeds opts.maxUnzippedBytes (default 250 MiB). This prevents shipping a bundle that Lambda will reject at deploy time with a less clear error.

Source

Thrown at packages/aws-lambda/scripts/build-zip.ts:148

  stageFfmpeg(stagingDir);

  // 3b. Stage the hyperframe runtime manifest + IIFE as siblings of
  //     handler.mjs. The producer's `hyperframeRuntimeLoader` checks
  //     SIBLING_MANIFEST_PATH first, so dropping the manifest alongside
  //     the bundled handler at /var/task/hyperframe.manifest.json lets
  //     renderChunk find it without needing PRODUCER_HYPERFRAME_MANIFEST_PATH.
  stageHyperframeRuntime(stagingDir);

  // 4. If we're on the chrome-headless-shell fallback, stage that binary.
  if (opts.source === "chrome-headless-shell") {
    stageChromeHeadlessShell(stagingDir);
  }

  // 5. Compute the unzipped size BEFORE zipping so we fail loud when over budget.
  const unzippedBytes = directorySizeBytes(stagingDir);
  console.log(`[build-zip] unzipped staging size: ${formatBytes(unzippedBytes)}`);
  if (unzippedBytes > opts.maxUnzippedBytes) {
    throw new Error(
      `[build-zip] unzipped bundle ${formatBytes(unzippedBytes)} exceeds limit ${formatBytes(
        opts.maxUnzippedBytes,
      )} (Lambda ZIP ceiling: 250 MiB unzipped). ` +
        `Switch --source to the lighter option, or move Chrome to a Lambda Layer.`,
    );
  }

  // 6. Build the ZIP.
  const zipPath = join(distDir, "handler.zip");
  zipDirectory(stagingDir, zipPath);
  const zippedBytes = statSync(zipPath).size;
  console.log(`[build-zip] zip size: ${formatBytes(zippedBytes)} → ${zipPath}`);
  if (zippedBytes > opts.maxZippedBytes) {
    throw new Error(
      `[build-zip] zip ${formatBytes(zippedBytes)} exceeds ZIP size limit ${formatBytes(
        opts.maxZippedBytes,
      )}.`,
    );

View on GitHub (pinned to c2996c8626)

Solutions

  1. Switch to --source=sparticuz (the lighter Chromium provider).
  2. Move Chrome and/or ffmpeg to a Lambda Layer instead of bundling in the ZIP.
  3. Raise the limit with --max-unzipped=<bytes> only if you are confident the Lambda unzipped ceiling does not apply (e.g., layers).

Example fix

// before
tsx build-zip.ts --source=chrome-headless-shell

// after
tsx build-zip.ts --source=sparticuz
Defensive patterns

Strategy: validation

Validate before calling

import { spawnSync } from "node:child_process";
const stagingSize = parseInt(
  spawnSync("du", ["-sb", stagingDir], { encoding: "utf-8" }).stdout.split(/\s+/)[0] ?? "0",
  10,
);
const LIMIT = 250 * 1024 * 1024;
if (stagingSize > LIMIT) {
  console.error(`Unzipped size ${stagingSize} exceeds ${LIMIT}. Switch to --source=sparticuz or use a Lambda Layer.`);
  process.exit(1);
}

Prevention

When it happens

Trigger: The staged directory (node_modules + ffmpeg + ffprobe + hyperframe runtime + optional Chrome binary) exceeds the unzipped byte limit. Most common when using --source=chrome-headless-shell, which adds ~150+ MiB of Chrome on top of ffmpeg and node_modules.

Common situations: Building with chrome-headless-shell source on a host where Chromium is large; dependency bloat from newly added packages; using the heavier source when sparticuz would suffice.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/d6a0582d7df0e88f. Report an issue: GitHub.