{"record":{"id":"eb258105714d1d73","repo":"heygen-com/hyperframes","slug":"failed-to-prepare-project-upload","errorCode":null,"errorMessage":"Failed to prepare project upload","messagePattern":"Failed to prepare project upload","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/publishProject.ts","lineNumber":640,"sourceCode":"      }),\n      headers: {\n        ...authHeaders,\n        \"content-type\": \"application/json\",\n      },\n      signal: AbortSignal.timeout(PUBLISH_METADATA_TIMEOUT_MS),\n    }),\n    \"Failed to prepare project upload\",\n    PUBLISH_TRANSPORT_ATTEMPTS,\n  );\n\n  if (uploadResponse.status === 404 || uploadResponse.status === 405) {\n    return null;\n  }\n\n  const uploadPayload = await readJson(uploadResponse);\n  const stagedUpload = parseStagedUploadResponse(uploadPayload, archive.buffer.byteLength);\n  if (!uploadResponse.ok || !stagedUpload) {\n    throw new Error(await readErrorMessage(uploadResponse, \"Failed to prepare project upload\"));\n  }\n\n  await uploadArchiveToPresignedUrl(stagedUpload, archive);\n\n  const completeResponse = await fetchForPublish(\n    `${apiBaseUrl}/v1/hyperframes/projects/publish/complete`,\n    () => ({\n      method: \"POST\",\n      body: JSON.stringify({\n        upload_key: stagedUpload.uploadKey,\n        file_name: fileName,\n        title,\n        ...(isPublic ? { is_public: true } : {}),\n        ...(projectId ? { project_id: projectId } : {}),\n      }),\n      headers: {\n        ...authHeaders,\n        \"content-type\": \"application/json\",","sourceCodeStart":622,"sourceCodeEnd":658,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/publishProject.ts#L622-L658","documentation":"publishProjectArchiveStaged: the initial POST to /v1/hyperframes/projects/publish/upload returned a non-OK HTTP status that is NOT 404 or 405 (those signal 'staged uploads unsupported' and cause a fallback to direct publish, returning null — not an error). So this fires on 400/401/403/500/etc. from the upload-preparation endpoint, OR when the response parsed but parseStagedUploadResponse returned null (missing upload_url or upload_key). readErrorMessage extracts the server message. This is the metadata stage of staged publishing, before any bytes are uploaded.","triggerScenarios":"Auth failure (401) on the staged-upload endpoint; rate limiting (429); the server rejects the content_type or content_length declared in the JSON body; a server bug returns 200 but omits upload_url/upload_key from the payload (parseStagedUploadResponse -> null); the staged-upload feature is partially deployed (returns 500 instead of 404).","commonSituations":"Expired credentials; a content_type the server does not accept; a content_length mismatch (archive changed between build and the upload-prep request); server-side incident affecting only the staged-upload endpoint; an anonymous publish hitting a staged-upload endpoint that requires auth.","solutions":["Read the embedded server message — it names the specific rejection reason.","For 401/403, re-authenticate and retry.","For 400, ensure content_type is application/zip and content_length matches the archive byte length.","For 5xx, retry after a short wait.","If staged uploads are genuinely unsupported, the server should return 404/405 (which fall back to direct publish); a 500 here is a server bug worth reporting."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await publishProjectArchive(projectDir, opts);\n} catch (err) {\n  if (err instanceof Error && /Failed to prepare project upload/.test(err.message)) {\n    // err.message includes server text; 401 -> re-auth, 5xx -> retry, else report\n    console.error('Staged upload prep failed:', err.message);\n  } else throw err;\n}","preventionTips":["Ensure credentials are valid before publish (re-auth on 401).","Keep content_type as application/zip and content_length matching the archive size.","If the server's staged-upload endpoint is flaky, a retry usually clears transient 5xx responses."],"tags":["publish","api","staged-upload","http","authentication","metadata"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}