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.

Solutions

  1. Do not call Complete after Terminate — check upload state before finalizing
  2. Restructure the flow so termination and completion are mutually exclusive (single owner of the lifecycle)
  3. If using defer, use a flag/checked completion: only Complete when the upload path succeeded
  4. 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

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


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/5c2fdad72b23067d. Report an issue: GitHub.

Appendix: 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)