heygen-com/hyperframes · error · Error

[s3Transport] s3 GetObject returned empty body for ${uri}

Error message

[s3Transport] s3 GetObject returned empty body for ${uri}

What it means

Thrown by `downloadS3ObjectToFile` when a `GetObjectCommand` succeeds (HTTP 200) but `response.Body` is undefined/null. S3 normally returns a stream; an absent body indicates a zero-byte object, an SDK deserialization quirk, or an edge condition, and the function refuses to write an empty file silently.

Source

Thrown at packages/aws-lambda/src/s3Transport.ts:78

  return { bucket, key };
}

/** Build `s3://bucket/key` from a location. */
export function formatS3Uri(loc: S3Location): string {
  return `s3://${loc.bucket}/${loc.key}`;
}

/** Stream an S3 object to a local file path. Throws if the body is missing. */
export async function downloadS3ObjectToFile(
  client: S3Client,
  uri: string,
  destPath: string,
): Promise<void> {
  const { bucket, key } = parseS3Uri(uri);
  const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
  const body = response.Body as NodeJS.ReadableStream | undefined;
  if (!body) {
    throw new Error(`[s3Transport] s3 GetObject returned empty body for ${uri}`);
  }
  mkdirSync(dirname(destPath), { recursive: true });
  await pipeline(body, createWriteStream(destPath));
}

/** Download and verify an immutable plan-v2 artifact before materialization. */
export async function downloadS3ObjectToFileVerified(
  client: S3Client,
  uri: string,
  destPath: string,
  expectedSha256: string,
): Promise<void> {
  assertSha256(expectedSha256);
  await downloadS3ObjectToFile(client, uri, destPath);
  const actual = await sha256File(destPath);
  if (actual !== expectedSha256) {
    rmSync(destPath, { force: true });
    const error = new Error(

View on GitHub (pinned to c2996c8626)

Solutions

  1. Inspect the object in S3 — `aws s3 ls s3://bucket/key` — to confirm it has content; re-upload if it's a 0-byte placeholder.
  2. Verify the key is correct and the object exists in the expected region/bucket.
  3. If a marker object is legitimate, special-case it at the caller rather than calling downloadS3ObjectToFile.
  4. Align @aws-sdk/client-s3 versions between producer and Lambda.

Example fix

// before: caller assumes non-empty body
await downloadS3ObjectToFile(s3, uri, dest);
// after: guard marker objects
const head = await s3.send(new HeadObjectCommand({ Bucket, Key }));
if ((head.ContentLength ?? 0) === 0) continue;
await downloadS3ObjectToFile(s3, uri, dest);
Defensive patterns

Strategy: validation

Validate before calling

import { HeadObjectCommand } from "@aws-sdk/client-s3";
async function assertObjectHasBody(client: S3Client, bucket: string, key: string): Promise<void> {
  const head = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
  if ((head.ContentLength ?? 0) === 0) {
    throw new Error(`s3 object is empty: s3://${bucket}/${key}`);
  }
}

Try / catch

try {
  await downloadS3ObjectToFile(client, uri, dest);
} catch (err) {
  if (err instanceof Error && /returned empty body/.test(err.message)) {
    // treat as a missing/stale object; re-upload or skip if it's a marker
  }
  throw err;
}

Prevention

When it happens

Trigger: `downloadS3ObjectToFile` where `client.send(new GetObjectCommand({...}))` returns a response with no `Body` — e.g. the object is a 0-byte marker, or the SDK version/model returned an unexpected shape.

Common situations: Object was created as a 0-byte marker (directory placeholder); an interrupted multipart upload left a partial object; SDK version mismatch where `Body` is wrapped differently; S3 EventBridge notification referencing a not-yet-consistent object.

Related errors


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