Tencent/WeKnora · error

failed to copy file in OBS: %w

Error message

failed to copy file in OBS: %w

What it means

CopyFile wraps the error from s3 CopyObject when the server-side copy within the same bucket fails. At this point the source key passed validation; the failure is in the OBS API call itself (source missing, permissions, size limits, or network).

Source

Thrown at internal/application/service/file/obs.go:276

	}

	ext := filepath.Ext(srcPath)
	var destKey string
	if s.pathPrefix != "" {
		destKey = fmt.Sprintf("%s/%d/%s/%s%s", s.pathPrefix, tenantID, knowledgeID, uuid.New().String(), ext)
	} else {
		destKey = fmt.Sprintf("%d/%s/%s%s", 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).
	_, 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 OBS: %w", err)
	}

	prefix := s.getPrifix()
	var newPath string
	if s.proxyDomain != "" {
		newPath = fmt.Sprintf("%s%s", prefix, destKey)
	} else {
		newPath = fmt.Sprintf("%s%s/%s", prefix, s.bucketName, destKey)
	}
	logger.Infof(ctx, "Copied OBS object %s to %s", srcPath, newPath)
	return newPath, nil
}

func (s *obsFileService) SaveBytes(ctx context.Context, data []byte, tenantID uint64, fileName string, temp bool) (string, error) {
	ext := filepath.Ext(fileName)

	var objectKey string
	if temp {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Unwrap and check for NoSuchKey — verify the source object still exists (HeadObject) and handle the race
  2. Ensure credentials have both s3:GetObject (source) and s3:PutObject (destination) permissions
  3. For objects near/above 5GB, perform a multipart copy instead of a single CopyObject
  4. Retry transient network errors with backoff; the operation is idempotent for a fixed destKey

Example fix

// before
if err != nil { return "", fmt.Errorf("failed to copy file in OBS: %w", err) }
// after
if err != nil {
    var nf *types.NotFound
    if errors.As(err, &nf) {
        return "", fmt.Errorf("source object disappeared before copy: %w", err)
    }
    if isRetryableNetErr(err) {
        return s.CopyFile(ctx, srcPath, tenantID, knowledgeID)
    }
    return "", fmt.Errorf("failed to copy file in OBS: %w", err)
}
Defensive patterns

Strategy: retry

Try / catch

newPath, err := svc.CopyFile(ctx, src, tenantID, kid)
if err != nil {
    if isRetryableNetErr(err) {
        newPath, err = svc.CopyFile(ctx, src, tenantID, kid) // idempotent per destKey
    }
    if err != nil {
        return fmt.Errorf("server-side copy failed: %w", err)
    }
}

Prevention

When it happens

Trigger: CopyObject fails because srcKey no longer exists (404), credentials lack both read on source and write on destination, the object exceeds the 5GB server-side copy limit, or a network/endpoint error occurs.

Common situations: Source object deleted between validation and copy (TOCTOU); IAM policy allowing PutObject but not GetObject; copying very large files that exceed the S3 CopyObject size limit; wrong region causing signature mismatch.

Related errors


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