heygen-com/hyperframes · error

[s3Transport] tar source must be an existing directory: ${so

Error message

[s3Transport] tar source must be an existing directory: ${sourceDir}

What it means

Thrown by tarDirectory when the sourceDir argument either does not exist or is not a directory (isDirectory() false). tarDirectory is the pack step used by deploySite and by the Lambda handler to gzip a planDir or project tree. Because the tar npm package is invoked with cwd: sourceDir, a non-directory path would cause a confusing ENOTDIR error inside the tar stream; this guard fails fast with the actual offending path.

Source

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

function isS3PreconditionFailed(error: unknown): boolean {
  if (!isRecord(error)) return false;
  const metadata = isRecord(error.$metadata) ? error.$metadata : undefined;
  return error.name === "PreconditionFailed" || metadata?.httpStatusCode === 412;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

/**
 * Pack a directory into a `.tar.gz` at `destTarball`. Uses the `tar` npm
 * package (pure JS over `node:zlib`) rather than spawning a system tar
 * binary — the AWS Lambda Node 22 base image ships a minimal set of
 * userland tools and does NOT include `tar` in `/usr/bin`.
 */
export async function tarDirectory(sourceDir: string, destTarball: string): Promise<void> {
  if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
    throw new Error(`[s3Transport] tar source must be an existing directory: ${sourceDir}`);
  }
  mkdirSync(dirname(destTarball), { recursive: true });
  await tar.create({ gzip: true, file: destTarball, cwd: sourceDir }, ["."]);
}

/**
 * Extract a `.tar.gz` produced by {@link tarDirectory} into `destDir`.
 * The directory is created (or cleared) before extraction so a retried
 * invocation doesn't observe stale files from a prior run on the same
 * warm Lambda container.
 */
export async function untarDirectory(tarballPath: string, destDir: string): Promise<void> {
  if (!existsSync(tarballPath)) {
    throw new Error(`[s3Transport] tarball missing: ${tarballPath}`);
  }
  // Wipe target so the warm container's prior planDir doesn't bleed into
  // the new invocation. Lambda re-uses /tmp across invocations on the same
  // container.

View on GitHub (pinned to c2996c8626)

Solutions

  1. Log statSync(sourceDir) or readdirSync before the call to confirm the path resolves to a directory in the current cwd.
  2. Use an absolute path (e.g. path.join(os.tmpdir(), ...)) rather than a relative one, especially inside Lambda.
  3. Ensure the directory-creation step (mkdirSync recursive) completed before tarDirectory is invoked.
  4. If the path is a symlink, resolve it first with fs.realpathSync to catch broken links.

Example fix

// before
await tarDirectory(projectDir, tarball);

// after
const abs = path.resolve(projectDir);
if (!existsSync(abs) || !statSync(abs).isDirectory()) {
  throw new Error(`projectDir not a directory: ${abs}`);
}
await tarDirectory(abs, tarball);
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
function assertTarrableDir(sourceDir: string): string {
  const abs = resolve(sourceDir);
  if (!existsSync(abs) || !statSync(abs).isDirectory()) {
    throw new Error(`not a directory: ${abs}`);
  }
  return abs;
}

Type guard

import { statSync } from 'node:fs';
const isDirectory = (p: string): boolean => {
  try { return statSync(p).isDirectory(); } catch { return false; }
};

Prevention

When it happens

Trigger: deploySite calls tarDirectory with opts.projectDir; the Lambda handler calls it with a planDir path. The error fires when the path points to a regular file, a broken symlink, or a path that was never created. Also triggered when a relative path resolves against the wrong working directory (Lambda cwd vs /tmp).

Common situations: Passing a tarball path instead of its unpacked directory; a planDir cleanup race where rmSync removed the dir between existence check and tar; relative paths on Lambda where cwd is /var/task but the artifact is under /tmp; symlink to a deleted target.

Related errors


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