{"record":{"id":"46922ee019c14a6f","repo":"heygen-com/hyperframes","slug":"failed-to-upload-project-archive","errorCode":null,"errorMessage":"Failed to upload project archive","messagePattern":"Failed to upload project archive","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/publishProject.ts","lineNumber":601,"sourceCode":"  stagedUpload: StagedUploadResponse,\n  archive: PublishArchiveResult,\n): Promise<void> {\n  const presignedUrlTtlMs = stagedUpload.expiresInSeconds * 1000 - PUBLISH_METADATA_TIMEOUT_MS;\n  const s3Response = await fetchForPublish(\n    stagedUpload.uploadUrl,\n    () => ({\n      method: \"PUT\",\n      body: new Blob([archiveArrayBuffer(archive)], { type: stagedUpload.contentType }),\n      headers: stagedUpload.uploadHeaders,\n      signal: AbortSignal.timeout(\n        Math.min(uploadTimeoutMs(archive.buffer.byteLength), presignedUrlTtlMs),\n      ),\n    }),\n    \"Failed to upload project archive\",\n    PUBLISH_TRANSPORT_ATTEMPTS,\n  );\n  if (!s3Response.ok) {\n    throw new Error(await readErrorMessage(s3Response, \"Failed to upload project archive\"));\n  }\n}\n\nasync function publishProjectArchiveStaged(\n  apiBaseUrl: string,\n  title: string,\n  archive: PublishArchiveResult,\n  isPublic: boolean,\n  authHeaders: Record<string, string>,\n  projectId: string | undefined,\n): Promise<PublishedProjectResponse | null> {\n  const fileName = `${title}.zip`;\n  const uploadResponse = await fetchForPublish(\n    `${apiBaseUrl}/v1/hyperframes/projects/publish/upload`,\n    () => ({\n      method: \"POST\",\n      body: JSON.stringify({\n        file_name: fileName,","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/publishProject.ts#L583-L619","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the error message — it includes S3's response body which names the specific error (SignatureDoesNotMatch, RequestTimeout, EntityTooLarge, etc.).","For clock skew (SignatureDoesNotMatch), sync the host clock (NTP) and retry.","For TTL expiry, retry — a fresh staged-upload request mints a new presigned URL.","For EntityTooLarge, reduce the archive size or check bucket limits.","For header/content-length issues, ensure no proxy is rewriting content-length or content-type headers."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import { execSync } from 'node:child_process';\n\nfunction checkClockSkew(): void {\n  // AWS SigV4 rejects signatures when host clock drifts beyond ~5 min\n  const drift = execSync('timedatectl show -p NTPSynchronized --value 2>/dev/null || true').toString();\n  // or compare against a known time API\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await publishProjectArchive(projectDir, opts);\n} catch (err) {\n  if (err instanceof Error && /Failed to upload project archive/.test(err.message)) {\n    // presigned URLs are single-use & TTL-bound — a fresh publish mints a new one\n    if (/ExpiredToken|RequestTimeout|SignatureDoesNotMatch/.test(err.message)) {\n      await new Promise(r => setTimeout(r, 1000));\n      return publishProjectArchive(projectDir, opts);\n    }\n  }\n  throw err;\n}","preventionTips":["Keep host clocks synced (NTP) to avoid AWS SigV4 SignatureDoesNotMatch from clock skew.","On slow networks, the presigned URL TTL can lapse — retry gets a fresh URL.","Ensure no proxy rewrites content-length or content-type headers on the S3 PUT."],"tags":["publish","s3","upload","presigned-url","http","aws"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}