Tencent/WeKnora · error

s3 copy rejected source %q: %w

Error message

s3 copy rejected source %q: %w

What it means

CopyFile rejects a source path that is not a valid s3:// URL by wrapping ErrCrossBackendCopy with 's3 copy rejected source %q'. parseS3FilePath failed, meaning the source belongs to a different storage backend (local disk, TOS) or is malformed. Copying across backends is intentionally unsupported.

Source

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

		Bucket: aws.String(s.bucketName),
		Key:    aws.String(objectName),
	})
	if err != nil {
		return fmt.Errorf("failed to delete file: %w", err)
	}

	return nil
}

// CopyFile copies an existing S3 object to a new knowledge-owned object using a
// server-side CopyObject (no data leaves S3). The destination uses the same
// layout as SaveFile. Returns ErrCrossBackendCopy when srcPath is not an s3:// path.
func (s *s3FileService) CopyFile(ctx context.Context,
	srcPath string, tenantID uint64, knowledgeID string,
) (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)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Ensure srcPath is the full path returned by SaveFile/SaveBytes (starts with s3://bucket/key)
  2. Check the storage config has not changed since the source file was created; migrate the file first
  3. Treat ErrCrossBackendCopy explicitly and copy via download+re-upload to the target backend
  4. Log the offending srcPath to identify where the non-S3 path came from

Example fix

// before
newPath, err := svc.CopyFile(ctx, oldPath, tenantID, knowledgeID) // oldPath is local
// after
if !strings.HasPrefix(oldPath, "s3://") {
    data, err := localSvc.GetFile(ctx, oldPath)
    // then SaveBytes via the S3 service instead of CopyFile
}
newPath, err := svc.CopyFile(ctx, oldPath, tenantID, knowledgeID)
Defensive patterns

Strategy: validation

Validate before calling

if !strings.HasPrefix(srcPath, "s3://") {
    return fmt.Errorf("%w: %s", file.ErrCrossBackendCopy, srcPath)
}

Type guard

func isS3Path(p string) bool { return strings.HasPrefix(p, "s3://") }

Try / catch

newPath, err := svc.CopyFile(ctx, srcPath, tenantID, kid)
if errors.Is(err, file.ErrCrossBackendCopy) {
    // fall back to download + re-upload via target backend
}

Prevention

When it happens

Trigger: Calling CopyFile with srcPath not starting with 's3://' (e.g. a local file path or a TOS path), or a malformed s3:// URL that parseS3FilePath cannot extract a key from.

Common situations: Mixing storage backends after migrating config (files stored locally but backend switched to S3); passing a raw key instead of a full s3:// URL; knowledge entries created under a previous local-storage configuration.

Related errors


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