Billionmail/BillionMail · error

open file for upload: %w

Error message

open file for upload: %w

What it means

UploadFile opens the local file before streaming it to Cloudflare R2, and wraps any os.Open failure with 'open file for upload: %w'. The wrapped error is Go's *PathError (open <path>: no such file or directory / permission denied), preserving the offending path and OS reason.

Source

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

				URL: R2Endpoint(cfg.AccountID),
			}, nil
		},
	)

	return s3.NewFromConfig(aws.Config{
		Region:                      "auto",
		Credentials:                 credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.AccessKeySecret, ""),
		EndpointResolverWithOptions: r2Resolver,
	}, func(o *s3.Options) {
		o.UsePathStyle = true
	})
}

// UploadFile uploads a local file to R2 and returns the public URL.
func UploadFile(ctx context.Context, cfg R2Config, localPath, contactID string) (*UploadResult, error) {
	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)
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. os.Stat the localPath first and return a clear 'file not found' message if it is missing, then retry the upload
  2. Verify the generation step that produced the video/thumbnail actually succeeded and wrote to the expected output path
  3. Check file permissions and that the worker process user can read the file
  4. Fix empty/relative path construction in the caller (log the resolved absolute path)
  5. Use errors.Is(err, os.ErrNotExist)/errors.Is(err, fs.ErrPermission) on the unwrapped error to branch precisely

Example fix

// before
f, err := os.Open(localPath)
if err != nil {
    return nil, fmt.Errorf("open file for upload: %w", err)
}
// after
if info, statErr := os.Stat(localPath); statErr != nil || info.IsDir() {
    return nil, fmt.Errorf("local file missing or not a regular file: %s", localPath)
}
f, err := os.Open(localPath)
if err != nil {
    return nil, fmt.Errorf("open file for upload: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

func canUpload(localPath string) error {
    info, err := os.Stat(localPath)
    if err != nil {
        return fmt.Errorf("file missing: %s", localPath)
    }
    if info.IsDir() {
        return fmt.Errorf("not a regular file: %s", localPath)
    }
    if localPath == "" {
        return fmt.Errorf("empty upload path")
    }
    return nil
}

Try / catch

res, err := video_gen.UploadFile(ctx, cfg, localPath, contactID)
if err != nil {
    switch {
    case errors.Is(err, os.ErrNotExist):
        log.Errorf("upload skipped, file missing: %v", err)
    case errors.Is(err, fs.ErrPermission):
        log.Errorf("upload blocked by permissions: %v", err)
    default:
        return err
    }
}

Prevention

When it happens

Trigger: os.Open(localPath) fails because the file does not exist (TestUploadFile_NonexistentFile, TestIntegration_UploadFile_NonexistentPath_ReturnsWrappedError), localPath is an empty string (TestUploadFile_EmptyPath, yielding 'open : no such file or directory'), the path is a directory, or the process lacks read permission.

Common situations: An upstream thumbnail/video generation step failed silently so the file was never written; misconfigured artifact directory or relative vs absolute path confusion; running the worker as a different user without read access; typo'd or empty contact-scoped file paths.

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/da9b1fe87fc63f48. Report an issue: GitHub.