{"record":{"id":"a606a35c9f647915","repo":"hcengineering/platform","slug":"cannot-complete-upload-with-no-parts","errorCode":null,"errorMessage":"cannot complete upload with no parts","messagePattern":"cannot complete upload with no parts","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"foundations/stream/internal/pkg/mediaconvert/multipart.go","lineNumber":174,"sourceCode":"\n\t// flush any remaining data as last part\n\tif w.buffer.Len() > 0 {\n\t\tpartNum := w.nextPartNum\n\t\tlastData := w.buffer.Bytes()\n\n\t\tpart, err := w.storage.MultipartUploadPart(ctx, w.objectName, w.uploadID, partNum, lastData)\n\t\tif err != nil {\n\t\t\tw.logger.Error(\"multipart upload last part failed\", zap.Error(err), zap.Int(\"partNumber\", partNum))\n\t\t\treturn errors.Wrap(err, \"failed to upload last part\")\n\t\t}\n\n\t\tw.bytesUploaded += int64(len(lastData))\n\t\tw.parts = append(w.parts, *part)\n\t}\n\n\tif len(w.parts) == 0 {\n\t\tw.logger.Warn(\"cannot complete upload with no parts\")\n\t\treturn errors.New(\"cannot complete upload with no parts\")\n\t}\n\n\tif err := w.storage.MultipartUploadComplete(ctx, w.objectName, w.uploadID, w.parts); err != nil {\n\t\tw.logger.Error(\"multipart upload complete failed\", zap.Error(err))\n\t\treturn errors.Wrap(err, \"failed to complete multipart upload\")\n\t}\n\n\tw.completed = true\n\tw.logger.Info(\n\t\t\"multipart upload completed\",\n\t\tzap.Int64(\"bytesUploaded\", w.bytesUploaded),\n\t\tzap.Int64(\"bytesWritten\", w.bytesWritten),\n\t)\n\n\treturn nil\n}\n","sourceCodeStart":156,"sourceCodeEnd":191,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/stream/internal/pkg/mediaconvert/multipart.go#L156-L191","documentation":"Complete() validates that at least one part was uploaded before asking storage to compose the object. If the parts list is empty (nothing was flushed via Write), it logs a warning and returns this error instead of creating a zero-part upload that storage would reject.","triggerScenarios":"Calling Complete immediately after creating MultipartUpload without any successful Write; every Write failed (storage errors, context cancelled) so no parts accumulated; all buffered data was under the minimum part size and got dropped on flush failure.","commonSituations":"Empty file uploads not special-cased before multipart completion; storage outages causing all chunk uploads to silently fail before Complete; callers finishing an upload whose data pipeline never ran.","solutions":["Ensure at least one chunk is written (Write) before calling Complete; special-case zero-byte files if supported","Check earlier Write errors — this error usually masks a failed chunk pipeline","Verify buffer/part accounting: confirm writes actually appended to parts, not just the local buffer","Retry the whole upload from scratch if the source data is available"],"exampleFix":"// before\nupload, _ := NewMultipartUpload(...)\n_ = upload.Complete(ctx) // no parts written\n// after\nif err := upload.Write(ctx, data); err != nil { return err }\nreturn upload.Complete(ctx)","handlingStrategy":"validation","validationCode":"if len(data) == 0 {\n    return errors.New(\"refusing multipart upload with no data\")\n}\n// ensure at least one Write succeeded before Complete","typeGuard":null,"tryCatchPattern":"if err := upload.Complete(ctx); err != nil {\n    if strings.Contains(err.Error(), \"no parts\") {\n        return ErrEmptyUpload\n    }\n    return err\n}","preventionTips":["Check Write return errors — don't call Complete after failed writes","Special-case zero-byte uploads before using multipart","Track a hasData flag; assert before Complete","Alert on 'no parts' warnings in logs — they indicate upstream write failures"],"tags":["upload","multipart","go","validation"],"backgroundTag":"upload-empty-parts","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}