hcengineering/platform · error
Failed to complete multipart upload
Error message
Failed to complete multipart upload
What it means
multipartUploadComplete POSTs the collected parts list (partNumber + etag pairs) to `<baseUrl>/complete?uploadId=...`. If the server responds non-ok, it throws 'Failed to complete multipart upload'. At this point all 5MB parts were uploaded, so this failure means the server refused to finalize the object.
Source
Thrown at foundations/core/packages/storage-client/src/upload.ts:212
uploadId: string,
parts: Array<{ partNumber: number, etag: string }>,
signal?: AbortSignal
): Promise<void> {
const url = new URL(concatLink(baseUrl, '/complete'))
url.searchParams.set('uploadId', uploadId)
const response = await fetch(url, {
signal,
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers
},
body: JSON.stringify({ parts })
})
if (!response.ok) {
throw new Error('Failed to complete multipart upload')
}
}
async function multipartUploadPart (
baseUrl: string,
headers: Record<string, string>,
uploadId: string,
partNumber: number,
blob: Blob,
options?: FileStorageUploadOptions
): Promise<{ etag: string }> {
const url = new URL(concatLink(baseUrl, '/part'))
url.searchParams.set('uploadId', uploadId)
url.searchParams.set('partNumber', `${partNumber}`)
const result = await uploadXhr(
{
url: url.toString(),View on GitHub (pinned to 63e28dc964)
Solutions
- Log response.status and body from /complete to get the server's reason (invalid parts vs expired uploadId vs auth).
- Ensure the token is refreshed or long-lived enough to outlast the entire multipart transfer.
- Verify parts are pushed in strictly ascending partNumber order, each with a non-empty etag, before complete is called.
- On failure, restart the whole uploadMultipart (the library aborts the old session automatically) rather than retrying /complete alone.
Example fix
// before: long upload with a token that expires mid-transfer
await uploadMultipart({ url, headers: { Authorization: `Bearer ${token}` }, body: hugeFile })
// after: keep auth fresh across the transfer
const headers = () => ({ Authorization: `Bearer ${getFreshToken()}` })
await uploadMultipart({ url, headers: headers(), body: hugeFile }) // refresh token before starting long uploads Defensive patterns
Strategy: retry
Validate before calling
// ensure parts are complete and ordered before finalize (library does this, but callers wrapping it can check)
const ordered = parts.every((p, i) => p.partNumber === i + 1 && typeof p.etag === 'string' && p.etag.length > 0)
if (!ordered) throw new Error('Invalid parts list before complete') Type guard
function isValidParts (v: unknown): v is Array<{ partNumber: number, etag: string }> {
return Array.isArray(v) && v.every(p => typeof p?.partNumber === 'number' && typeof p?.etag === 'string')
} Try / catch
try {
await uploadMultipart({ url, headers, body: file }, { signal })
} catch (err) {
if (err instanceof Error && err.message === 'Failed to complete multipart upload') {
// whole-session retry: abort happened server-side, restart uploadMultipart from scratch
await uploadMultipart({ url, headers: refreshedHeaders(), body: file }, { signal })
} else throw err
} Prevention
- Use a long-lived or refreshed token for uploads that will outlast the transfer.
- Retry the entire multipart session, not just the /complete call.
- Keep part uploads strictly sequential (as the library does) to keep partNumbers ordered.
- Check proxy/ingress timeouts exceed the expected transfer duration.
When it happens
Trigger: All parts uploaded but POST /complete returns non-ok: 400 (parts list invalid, out of order, or a missing etag), 404 (uploadId expired/aborted server-side or wrong), 401/403 (token expired during a long upload), 409/500 on server-side object assembly.
Common situations: Very large files taking long enough for the token or the server-side multipart session to expire; a part retried and registered with a duplicate partNumber; proxy timing out on the complete call; uploadId mismatch because baseUrl changed mid-upload.
Related errors
- Failed to initialize multipart upload
- Failed to parse response for part ${partNumber}
- Failed to reject multipart upload
- missing files
- response.statusText
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/528eb963b865434b.
Report an issue: GitHub.