hcengineering/platform · error
cannot complete terminated upload
Error message
cannot complete terminated upload
What it means
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.
Source
Thrown at foundations/stream/internal/pkg/mediaconvert/multipart.go:152
if err := w.storage.MultipartUploadCancel(cancelCtx, w.objectName, w.uploadID); err != nil {
w.logger.Error("multipart upload cancel failed", zap.Error(err))
return errors.Wrap(err, "failed to cancel multipart upload")
}
w.logger.Debug("multipart upload terminated", zap.Int("parts", len(w.parts)))
return nil
}
// Complete uploads last bytes and completes the upload
func (w *MultipartUpload) Complete(ctx context.Context) error {
if w.completed {
return nil
}
if w.terminated {
return errors.New("cannot complete terminated upload")
}
w.logger.Debug("finishing multipart upload", zap.Int("parts", len(w.parts)))
// flush any remaining data as last part
if w.buffer.Len() > 0 {
partNum := w.nextPartNum
lastData := w.buffer.Bytes()
part, err := w.storage.MultipartUploadPart(ctx, w.objectName, w.uploadID, partNum, lastData)
if err != nil {
w.logger.Error("multipart upload last part failed", zap.Error(err), zap.Int("partNumber", partNum))
return errors.Wrap(err, "failed to upload last part")
}
w.bytesUploaded += int64(len(lastData))
w.parts = append(w.parts, *part)
}View on GitHub (pinned to 63e28dc964)
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
Example fix
// before
defer upload.Complete(ctx) // fires even after Terminate
// after
if success {
err = upload.Complete(ctx)
} else {
_ = upload.Terminate(ctx)
} Defensive patterns
Strategy: validation
Validate before calling
if !upload.terminated && !upload.completed {
if err := upload.Complete(ctx); err != nil { /* handle */ }
} Type guard
func completable(u *MultipartUpload) bool {
return !u.terminated && !u.completed
} Try / catch
if err := upload.Complete(ctx); err != nil {
if strings.Contains(err.Error(), "terminated") {
// upload was aborted; treat as cancelled, not a bug
return ErrUploadCancelled
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- upload already terminated or completed
- cannot complete upload with no parts
- Failed to initialize multipart upload
- Failed to complete multipart upload
- Failed to parse response for part ${partNumber}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/5c2fdad72b23067d.
Report an issue: GitHub.