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

  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.