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
- 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.
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
- 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.
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
- [s3Transport] upload source missing: ${localPath}
- Direct upload PUT failed: ${res.status} ${res.statusText}${d
- Failed to publish project
- Failed to prepare project upload
- [s3Transport] expected s3:// URI, got: ${JSON.stringify(uri)
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/46922ee019c14a6f.
Report an issue: GitHub.