{"record":{"id":"5c2fdad72b23067d","repo":"hcengineering/platform","slug":"cannot-complete-terminated-upload","errorCode":null,"errorMessage":"cannot complete terminated upload","messagePattern":"cannot complete terminated upload","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"foundations/stream/internal/pkg/mediaconvert/multipart.go","lineNumber":152,"sourceCode":"\n\tif err := w.storage.MultipartUploadCancel(cancelCtx, w.objectName, w.uploadID); err != nil {\n\t\tw.logger.Error(\"multipart upload cancel failed\", zap.Error(err))\n\t\treturn errors.Wrap(err, \"failed to cancel multipart upload\")\n\t}\n\n\tw.logger.Debug(\"multipart upload terminated\", zap.Int(\"parts\", len(w.parts)))\n\n\treturn nil\n}\n\n// Complete uploads last bytes and completes the upload\nfunc (w *MultipartUpload) Complete(ctx context.Context) error {\n\tif w.completed {\n\t\treturn nil\n\t}\n\n\tif w.terminated {\n\t\treturn errors.New(\"cannot complete terminated upload\")\n\t}\n\n\tw.logger.Debug(\"finishing multipart upload\", zap.Int(\"parts\", len(w.parts)))\n\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}","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/stream/internal/pkg/mediaconvert/multipart.go#L134-L170","documentation":"Complete() refuses to finalize a multipart upload that was previously terminated: if completed is true it's a no-op returning nil, but if terminated is true it returns this error, because there are no parts in storage to compose into an object.","triggerScenarios":"Calling Complete on an upload after Terminate/Abort was called (e.g. user cancelled then the finalizer still fires); a deferred Complete running alongside an error-path Terminate in the same flow.","commonSituations":"Error-handling paths that terminate the upload but a defer/finally still calls Complete; client disconnects triggering termination while the server-side finish handler proceeds; race between timeout-based termination and completion.","solutions":["Do not call Complete after Terminate — check upload state before finalizing","Restructure the flow so termination and completion are mutually exclusive (single owner of the lifecycle)","If using defer, use a flag/checked completion: only Complete when the upload path succeeded","Track the error that caused termination and surface it instead of calling Complete"],"exampleFix":"// before\ndefer upload.Complete(ctx) // fires even after Terminate\n// after\nif success {\n    err = upload.Complete(ctx)\n} else {\n    _ = upload.Terminate(ctx)\n}","handlingStrategy":"validation","validationCode":"if !upload.terminated && !upload.completed {\n    if err := upload.Complete(ctx); err != nil { /* handle */ }\n}","typeGuard":"func completable(u *MultipartUpload) bool {\n    return !u.terminated && !u.completed\n}","tryCatchPattern":"if err := upload.Complete(ctx); err != nil {\n    if strings.Contains(err.Error(), \"terminated\") {\n        // upload was aborted; treat as cancelled, not a bug\n        return ErrUploadCancelled\n    }\n    return err\n}","preventionTips":["Make Terminate and Complete mutually exclusive in the flow","Avoid bare defer Complete — use explicit success-path completion","Track cancellation so finalizers know not to Complete","Use context cancellation to skip finalization on abort"],"tags":["upload","multipart","lifecycle","go"],"backgroundTag":"upload-already-terminated","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}