Tencent/WeKnora · error

failed to upload bytes to S3: %w

Error message

failed to upload bytes to S3: %w

What it means

SaveBytes wraps errors from the S3 PutObject upload of the byte payload. The reader and metadata were constructed but the upload itself failed — permissions, size/quota limits, connectivity, or encryption configuration. Nothing is persisted and no s3:// path is returned.

Source

Thrown at internal/application/service/file/s3.go:343

func (s *s3FileService) SaveBytes(ctx context.Context, data []byte, tenantID uint64, fileName string, temp bool) (string, error) {
	safeName, err := utils.SafeFileName(fileName)
	if err != nil {
		return "", fmt.Errorf("invalid file name: %w", err)
	}
	ext := filepath.Ext(safeName)
	objectName := fmt.Sprintf("%s%d/exports/%s%s", s.pathPrefix, tenantID, uuid.New().String(), ext)

	// Upload bytes to S3
	reader := bytes.NewReader(data)
	_, err = s.client.PutObject(ctx, &s3.PutObjectInput{
		Bucket:        aws.String(s.bucketName),
		Key:           aws.String(objectName),
		Body:          reader,
		ContentLength: aws.Int64(int64(len(data))),
		ContentType:   aws.String(utils.GetContentTypeByExt(ext)),
	})
	if err != nil {
		return "", fmt.Errorf("failed to upload bytes to S3: %w", err)
	}

	return fmt.Sprintf("s3://%s/%s", s.bucketName, objectName), nil
}

// GetFileURL returns a presigned download URL for the file
func (s *s3FileService) GetFileURL(ctx context.Context, filePath string) (string, error) {
	objectName, err := s.parseS3FilePath(filePath)
	if err != nil {
		return "", err
	}

	// Create presign client
	presignClient := s3.NewPresignClient(s.client)

	// Generate presigned URL
	presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(s.bucketName),

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Verify IAM s3:PutObject permission on pathPrefix/tenantID/exports/*
  2. Unwrap errors.As(smithy.APIError) to identify the exact S3 error code
  3. Check object size against bucket quotas and S3 limits, and confirm KMS key grants if SSE-KMS is enabled
  4. Confirm network/endpoint configuration and retry transient failures with backoff

Example fix

// before
path, err := svc.SaveBytes(ctx, data, tenantID, name, false)
// after
path, err := svc.SaveBytes(ctx, data, tenantID, name, false)
if err != nil {
    var apiErr smithy.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode() == "AccessDenied" {
        return fmt.Errorf("check s3:PutObject on %s/exports/*: %w", tenantID, err)
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

if len(data) == 0 { return errors.New("empty payload") }
if _, err := utils.SafeFileName(fileName); err != nil { return err }

Try / catch

var path string
err := retry.Do(3, backoff, func() error {
    var e error
    path, e = svc.SaveBytes(ctx, data, tenantID, name, false)
    var apiErr smithy.APIError
    if e != nil && errors.As(e, &apiErr) && isTransient(apiErr.ErrorCode()) {
        return e // retry
    }
    return retry.Stop(e)
})

Prevention

When it happens

Trigger: PutObject failing due to AccessDenied on the tenant prefix, payload exceeding bucket quota or max object size, KMS key access denied, or network failure to the S3 endpoint.

Common situations: IAM policy not granting PutObject on the exports/ prefix; large export files hitting bucket lifecycle/quota limits; SSE-KMS bucket where the role lacks kms:GenerateDataKey; endpoint unreachable from a private subnet.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/d0d6e37854edb0fe. Report an issue: GitHub.