{"record":{"id":"40144a49f393f74e","repo":"Billionmail/BillionMail","slug":"r2-upload-w","errorCode":null,"errorMessage":"r2 upload: %w","messagePattern":"r2 upload: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/upload.go","lineNumber":97,"sourceCode":"\tf, err := os.Open(localPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open file for upload: %w\", err)\n\t}\n\tdefer f.Close()\n\n\tfilename := filepath.Base(localPath)\n\tkey := BuildR2ObjectKey(contactID, filename)\n\tcontentType := detectContentType(filename)\n\n\tclient := NewR2Client(cfg)\n\t_, err = client.PutObject(ctx, &s3.PutObjectInput{\n\t\tBucket:      aws.String(cfg.BucketName),\n\t\tKey:         aws.String(key),\n\t\tBody:        f,\n\t\tContentType: aws.String(contentType),\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"r2 upload: %w\", err)\n\t}\n\n\treturn &UploadResult{\n\t\tKey:       key,\n\t\tPublicURL: BuildPublicURL(cfg, key),\n\t}, nil\n}\n\n// UploadVideoAssets uploads the video and thumbnail to R2.\n// Returns public URLs for both.\nfunc UploadVideoAssets(ctx context.Context, cfg R2Config, videoPath, thumbnailPath, contactID string) (videoURL, thumbURL string, err error) {\n\tvideoResult, err := UploadFile(ctx, cfg, videoPath, contactID)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"upload video: %w\", err)\n\t}\n\n\tthumbResult, err := UploadFile(ctx, cfg, thumbnailPath, contactID)\n\tif err != nil {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/upload.go#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\n_, err := client.PutObject(ctx, &s3.PutObjectInput{...})\nif err != nil {\n    return nil, fmt.Errorf(\"r2 upload: %w\", err)\n}\n// after\nvar ae smithy.APIError\n_, err := client.PutObject(ctx, &s3.PutObjectInput{...})\nif err != nil {\n    if errors.As(err, &ae) && ae.ErrorCode() == \"NoSuchBucket\" {\n        return nil, fmt.Errorf(\"r2 bucket %q does not exist\", cfg.BucketName)\n    }\n    return nil, fmt.Errorf(\"r2 upload: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if cfg.BucketName == \"\" || cfg.AccessKeyID == \"\" || cfg.SecretAccessKey == \"\" || cfg.AccountID == \"\" {\n    return fmt.Errorf(\"incomplete R2 config\")\n}\nendpoint := fmt.Sprintf(\"https://%s.r2.cloudflarestorage.com\", cfg.AccountID)\nif _, err := url.Parse(endpoint); err != nil {\n    return fmt.Errorf(\"invalid R2 endpoint: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"res, err := video_gen.UploadFile(ctx, cfg, localPath, contactID)\nif err != nil {\n    var oe *smithy.OperationError\n    if errors.As(err, &oe) {\n        log.Errorf(\"r2 upload failed: %v (%v)\", oe, oe.Unwrap())\n        if isRetryable(oe) {\n            // retry with backoff\n        }\n    }\n}","preventionTips":["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"],"tags":["aws-sdk","r2","s3","upload","network"],"backgroundTag":"s3-upload-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}