Tencent/WeKnora · error

failed to copy file in S3: %w

Error message

failed to copy file in S3: %w

What it means

CopyFile wraps errors from the S3 CopyObject API call. The source key parsed successfully but the server-side copy failed — commonly missing s3:GetObject on the source, missing s3:PutObject on the destination, or object >5GB requiring multipart copy. The S3 error is chained via %w.

Source

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

) (string, error) {
	srcKey, err := s.parseS3FilePath(srcPath)
	if err != nil {
		return "", fmt.Errorf("s3 copy rejected source %q: %w", srcPath, ErrCrossBackendCopy)
	}

	ext := filepath.Ext(srcPath)
	destKey := fmt.Sprintf("%s%d/%s/%s%s", s.pathPrefix, tenantID, knowledgeID, uuid.New().String(), ext)

	// CopySource is "bucket/key"; the '/' separators must NOT be percent-encoded
	// (url.PathEscape would turn them into %2F and break the bucket/key split).
	// srcKey is already validated by parseS3FilePath -> SafeObjectKey.
	_, err = s.client.CopyObject(ctx, &s3.CopyObjectInput{
		Bucket:     aws.String(s.bucketName),
		CopySource: aws.String(s.bucketName + "/" + srcKey),
		Key:        aws.String(destKey),
	})
	if err != nil {
		return "", fmt.Errorf("failed to copy file in S3: %w", err)
	}

	newPath := fmt.Sprintf("s3://%s/%s", s.bucketName, destKey)
	logger.Infof(ctx, "Copied S3 object %s to %s", srcPath, newPath)
	return newPath, nil
}

// SaveBytes saves bytes data to S3 and returns the file path
// temp parameter is ignored for S3 (no auto-expiration support in this implementation)
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

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Grant the IAM role both s3:GetObject on the source key and s3:PutObject on the destination prefix
  2. Unwrap errors.As(smithy.APIError) to see the exact S3 error code (AccessDenied/NoSuchKey/InvalidRequest)
  3. For objects >5GB, implement multipart upload copy (UploadPartCopy) instead of single CopyObject
  4. For SSE-KMS buckets, verify kms:Decrypt and kms:Encrypt grants on the key

Example fix

// before
newPath, err := svc.CopyFile(ctx, src, tenantID, kid)
if err != nil { return err }
// after
newPath, err := svc.CopyFile(ctx, src, tenantID, kid)
if err != nil {
    var apiErr smithy.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode() == "InvalidRequest" {
        // fall back to multipart copy for large objects
    }
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

newPath, err := svc.CopyFile(ctx, srcPath, tenantID, kid)
if err != nil {
    var apiErr smithy.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode() == "AccessDenied" {
        return fmt.Errorf("missing s3 permissions for copy: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: CopyObject failing due to AccessDenied on source or destination, NoSuchKey on srcKey, KMS encryption key permission issues, or CopySource size exceeding the 5GB single-request limit.

Common situations: KMS-encrypted objects copied by a principal lacking kms:Decrypt/kms:Encrypt; cross-account or cross-region source without proper permissions; very large uploaded files breaking the 5GB CopyObject limit.

Related errors


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