Billionmail/BillionMail · error

upload thumbnail: %w

Error message

upload thumbnail: %w

What it means

The thumbnail stage of UploadVideoAssets wraps UploadFile failures for thumbnailPath with 'upload thumbnail: %w', mirroring the video-stage wrapper so logs identify which asset failed. The full underlying cause (file open or R2 error) remains reachable via errors.Unwrap/errors.As.

Source

Thrown at core/internal/service/video_gen/upload.go:116

	}

	return &UploadResult{
		Key:       key,
		PublicURL: BuildPublicURL(cfg, key),
	}, nil
}

// UploadVideoAssets uploads the video and thumbnail to R2.
// Returns public URLs for both.
func UploadVideoAssets(ctx context.Context, cfg R2Config, videoPath, thumbnailPath, contactID string) (videoURL, thumbURL string, err error) {
	videoResult, err := UploadFile(ctx, cfg, videoPath, contactID)
	if err != nil {
		return "", "", fmt.Errorf("upload video: %w", err)
	}

	thumbResult, err := UploadFile(ctx, cfg, thumbnailPath, contactID)
	if err != nil {
		return "", "", fmt.Errorf("upload thumbnail: %w", err)
	}

	return videoResult.PublicURL, thumbResult.PublicURL, nil
}

// detectContentType returns the MIME type based on file extension.
func detectContentType(filename string) string {
	ext := strings.ToLower(filepath.Ext(filename))
	switch ext {
	case ".mp4":
		return "video/mp4"
	case ".webm":
		return "video/webm"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".gif":

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped chain: if it is 'open file for upload', the thumbnail was never generated — rerun GenerateThumbnail and surface its error instead of ignoring it
  2. Verify the thumbnailPath matches the OutputPath returned by GenerateThumbnail
  3. os.Stat thumbnailPath before the upload stage and fail fast with a clear message
  4. If R2-side, apply the same endpoint/credential/bucket checks as the 'r2 upload' error
  5. Compare with the video-stage result: if video succeeded, the R2 config is fine and the problem is local to the thumbnail file

Example fix

// before
thumbResult, err := UploadFile(ctx, cfg, thumbnailPath, contactID)
if err != nil {
    return "", "", fmt.Errorf("upload thumbnail: %w", err)
}
// after
if _, err := os.Stat(thumbnailPath); err != nil {
    return "", "", fmt.Errorf("thumbnail file missing (was GenerateThumbnail run?): %s", thumbnailPath)
}
thumbResult, err := UploadFile(ctx, cfg, thumbnailPath, contactID)
if err != nil {
    return "", "", fmt.Errorf("upload thumbnail: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

if thumbPath == "" {
    return fmt.Errorf("no thumbnail: GenerateThumbnail likely failed upstream")
}
if _, err := os.Stat(thumbPath); err != nil {
    return fmt.Errorf("thumbnail missing at %s", thumbPath)
}

Try / catch

vURL, tURL, err := video_gen.UploadVideoAssets(ctx, cfg, videoPath, thumbPath, contactID)
if err != nil {
    if strings.Contains(fmt.Sprint(err), "upload thumbnail") || errors.Is(err, os.ErrNotExist) {
        log.Errorf("thumbnail stage failed: %v", err)
        // re-run GenerateThumbnail before retrying the upload
    }
}

Prevention

When it happens

Trigger: UploadFile(ctx, cfg, thumbnailPath, contactID) fails because the thumbnail file does not exist (TestUploadVideoAssets_SecondFileMissing — typically because GenerateThumbnail failed earlier) or the R2 PutObject for the thumbnail errors.

Common situations: GenerateThumbnail returned an error that was logged-and-ignored upstream, leaving no thumbnail on disk; wrong thumbnail output path configured; R2 issues that also would have failed the video upload; context cancelled between the two uploads.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/b00085c6afa9f438. Report an issue: GitHub.