Billionmail/BillionMail · error
r2 upload: %w
Error message
r2 upload: %w
What it means
After opening the local file, UploadFile calls the AWS S3-compatible PutObject client against Cloudflare R2; any client-side failure is wrapped as 'r2 upload: %w'. This is the S3 SDK's operation error (e.g. *smithy.OperationError or *types.*Error) carrying the transport-level cause.
Source
Thrown at core/internal/service/video_gen/upload.go:97
f, err := os.Open(localPath)
if err != nil {
return nil, fmt.Errorf("open file for upload: %w", err)
}
defer f.Close()
filename := filepath.Base(localPath)
key := BuildR2ObjectKey(contactID, filename)
contentType := detectContentType(filename)
client := NewR2Client(cfg)
_, err = client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(cfg.BucketName),
Key: aws.String(key),
Body: f,
ContentType: aws.String(contentType),
})
if err != nil {
return nil, fmt.Errorf("r2 upload: %w", err)
}
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 {View on GitHub (pinned to fc36c76c05)
Solutions
- Log the full wrapped error chain (errors.Unwrap / %+v) to get the S3 error code, then correct the specific cause (endpoint, credentials, or bucket)
- Verify cfg in R2Config: AccountID, AccessKeyID, SecretAccessKey and BucketName against the Cloudflare dashboard and a tool like 'aws s3 cp' with the R2 endpoint
- Confirm the R2 API token has write permission on the bucket
- Check container egress networking/DNS to <account>.r2.cloudflarestorage.com
- If transient (timeout/5xx), retry with backoff before failing the pipeline
Example fix
// before
_, err := client.PutObject(ctx, &s3.PutObjectInput{...})
if err != nil {
return nil, fmt.Errorf("r2 upload: %w", err)
}
// after
var ae smithy.APIError
_, err := client.PutObject(ctx, &s3.PutObjectInput{...})
if err != nil {
if errors.As(err, &ae) && ae.ErrorCode() == "NoSuchBucket" {
return nil, fmt.Errorf("r2 bucket %q does not exist", cfg.BucketName)
}
return nil, fmt.Errorf("r2 upload: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
if cfg.BucketName == "" || cfg.AccessKeyID == "" || cfg.SecretAccessKey == "" || cfg.AccountID == "" {
return fmt.Errorf("incomplete R2 config")
}
endpoint := fmt.Sprintf("https://%s.r2.cloudflarestorage.com", cfg.AccountID)
if _, err := url.Parse(endpoint); err != nil {
return fmt.Errorf("invalid R2 endpoint: %w", err)
} Try / catch
res, err := video_gen.UploadFile(ctx, cfg, localPath, contactID)
if err != nil {
var oe *smithy.OperationError
if errors.As(err, &oe) {
log.Errorf("r2 upload failed: %v (%v)", oe, oe.Unwrap())
if isRetryable(oe) {
// retry with backoff
}
}
} Prevention
- Validate R2Config fields at startup (endpoint, keys, bucket)
- Test credentials once with a HeadBucket/list call before processing jobs
- Add retry-with-backoff for 5xx/timeouts only
- Keep request contexts bounded but generous enough for large videos
When it happens
Trigger: PutObject returns an error: unreachable/bad AccountID endpoint, invalid or missing R2 access/secret keys (403 InvalidAccessKeyId/SignatureDoesNotMatch), nonexistent bucket name, network timeouts, or the reader failing mid-stream.
Common situations: Wrong endpoint built from a mistyped Cloudflare account ID; rotated or scope-less R2 API tokens; bucket name mismatch between config and Cloudflare dashboard; no outbound network/DNS from the container; request context cancelled (TestUploadFile_CancelledContext).
Related errors
- upload image failed: %s
- open file for upload: %w
- upload video: %w
- upload thumbnail: %w
- base URL not configured
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/40144a49f393f74e.
Report an issue: GitHub.