heygen-com/hyperframes · error

Failed to upload project archive

Error message

Failed to upload project archive

What it means

uploadArchiveToPresignedUrl: the PUT to the presigned S3 URL (stagedUpload.uploadUrl) returned a non-OK HTTP status. This is the second stage of staged publish — the metadata stage succeeded (got a presigned URL), but the actual binary upload to object storage failed at the HTTP layer. Distinct from [255] (transport throw, never got a Response) and from [259] (metadata-stage failure). The timeout is min(uploadTimeoutMs(byteLength), presignedUrlTtlMs), so a slow upload that exceeds the presigned URL's TTL also surfaces here.

Source

Thrown at packages/cli/src/utils/publishProject.ts:601

  stagedUpload: StagedUploadResponse,
  archive: PublishArchiveResult,
): Promise<void> {
  const presignedUrlTtlMs = stagedUpload.expiresInSeconds * 1000 - PUBLISH_METADATA_TIMEOUT_MS;
  const s3Response = await fetchForPublish(
    stagedUpload.uploadUrl,
    () => ({
      method: "PUT",
      body: new Blob([archiveArrayBuffer(archive)], { type: stagedUpload.contentType }),
      headers: stagedUpload.uploadHeaders,
      signal: AbortSignal.timeout(
        Math.min(uploadTimeoutMs(archive.buffer.byteLength), presignedUrlTtlMs),
      ),
    }),
    "Failed to upload project archive",
    PUBLISH_TRANSPORT_ATTEMPTS,
  );
  if (!s3Response.ok) {
    throw new Error(await readErrorMessage(s3Response, "Failed to upload project archive"));
  }
}

async function publishProjectArchiveStaged(
  apiBaseUrl: string,
  title: string,
  archive: PublishArchiveResult,
  isPublic: boolean,
  authHeaders: Record<string, string>,
  projectId: string | undefined,
): Promise<PublishedProjectResponse | null> {
  const fileName = `${title}.zip`;
  const uploadResponse = await fetchForPublish(
    `${apiBaseUrl}/v1/hyperframes/projects/publish/upload`,
    () => ({
      method: "POST",
      body: JSON.stringify({
        file_name: fileName,

View on GitHub (pinned to c2996c8626)

Solutions

  1. Read the error message — it includes S3's response body which names the specific error (SignatureDoesNotMatch, RequestTimeout, EntityTooLarge, etc.).
  2. For clock skew (SignatureDoesNotMatch), sync the host clock (NTP) and retry.
  3. For TTL expiry, retry — a fresh staged-upload request mints a new presigned URL.
  4. For EntityTooLarge, reduce the archive size or check bucket limits.
  5. For header/content-length issues, ensure no proxy is rewriting content-length or content-type headers.
Defensive patterns

Strategy: retry

Validate before calling

import { execSync } from 'node:child_process';

function checkClockSkew(): void {
  // AWS SigV4 rejects signatures when host clock drifts beyond ~5 min
  const drift = execSync('timedatectl show -p NTPSynchronized --value 2>/dev/null || true').toString();
  // or compare against a known time API
}

Try / catch

try {
  return await publishProjectArchive(projectDir, opts);
} catch (err) {
  if (err instanceof Error && /Failed to upload project archive/.test(err.message)) {
    // presigned URLs are single-use & TTL-bound — a fresh publish mints a new one
    if (/ExpiredToken|RequestTimeout|SignatureDoesNotMatch/.test(err.message)) {
      await new Promise(r => setTimeout(r, 1000));
      return publishProjectArchive(projectDir, opts);
    }
  }
  throw err;
}

Prevention

When it happens

Trigger: S3 returned 403 (presigned URL expired or signature mismatch — often a clock-skew or a TTL that elapsed during a slow upload); 413 (archive larger than the bucket/object limit); 400 (bad upload headers, e.g. content-length mismatch or missing x-amz-server-side-encryption); the presigned URL's signed headers included content-length but the uploaded byte count differed.

Common situations: Slow network causing the upload to exceed stagedUpload.expiresInSeconds (the TTL minus PUBLISH_METADATA_TIMEOUT_MS); host clock skew breaking AWS SigV4 signatures; a content-length header mismatch when archiveArrayBuffer byte length differs from what was declared; bucket policy rejecting the upload; corporate proxy stripping/mangling headers.

Related errors


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