googleapis/mcp-toolbox · error
failed to copy %q/%q to %q/%q: %w
Error message
failed to copy %q/%q to %q/%q: %w
What it means
CopyObject runs dst.CopierFrom(src).Run(ctx), a single GCS copy API call; this error wraps any failure of that call. The copy is atomic server-side — either the destination object is created from the source or nothing changes. Because the wrapped error is the raw GCS API error, its storage.ErrObjectNotExist / googleapi.Error codes directly identify which side (source or destination) is at fault.
Source
Thrown at internal/sources/cloudstorage/cloudstorage.go:556
}, nil
}
// CopyObject copies an object to a destination object. The destination may be
// in the same bucket or a different bucket. Existing destination objects are
// replaced, matching Cloud Storage's copy semantics without preconditions.
func (s *Source) CopyObject(ctx context.Context, sourceBucket, sourceObject, destinationBucket, destinationObject string) (map[string]any, error) {
if err := s.validateBucket(sourceBucket); err != nil {
return nil, err
}
if err := s.validateBucket(destinationBucket); err != nil {
return nil, err
}
src := s.client.Bucket(sourceBucket).Object(sourceObject)
dst := s.client.Bucket(destinationBucket).Object(destinationObject)
attrs, err := dst.CopierFrom(src).Run(ctx)
if err != nil {
return nil, fmt.Errorf("failed to copy %q/%q to %q/%q: %w", sourceBucket, sourceObject, destinationBucket, destinationObject, err)
}
return map[string]any{
"sourceBucket": sourceBucket,
"sourceObject": sourceObject,
"destinationBucket": destinationBucket,
"destinationObject": destinationObject,
"bytes": attrs.Size,
"contentType": attrs.ContentType,
}, nil
}
// MoveObject atomically renames or moves an object within the same bucket using
// Cloud Storage's native move API. Cross-bucket moves should be modeled as
// CopyObject followed by DeleteObject.
func (s *Source) MoveObject(ctx context.Context, bucket, sourceObject, destinationObject string) (map[string]any, error) {
if err := s.validateBucket(bucket); err != nil {
return nil, errView on GitHub (pinned to 8cc6e09de2)
Solutions
- Check errors.Is(err, storage.ErrObjectNotExist) to confirm a missing source object; verify the object path exists via ObjectHandle.Attrs before copying.
- Grant storage.objects.get on the source bucket and storage.objects.create on the destination bucket to the credentials.
- Verify both bucket names and that both buckets exist (BucketHandle.Attrs).
- If the wrapped googleapi.Error is 403 with a constraint/org-policy reason, align destination bucket policies (CMEK, location) with the source.
- On 429/5xx, retry the copy with backoff; CopyObject is safe to retry because the destination is overwritten atomically.
Example fix
// before: assume source exists
attrs, err := dst.CopierFrom(src).Run(ctx)
// after: pre-check source and classify errors
if _, err := s.client.Bucket(sourceBucket).Object(sourceObject).Attrs(ctx); err != nil {
return nil, fmt.Errorf("source %q/%q not found: %w", sourceBucket, sourceObject, err)
}
attrs, err := dst.CopierFrom(src).Run(ctx)
if err != nil {
var apiErr *googleapi.Error
if errors.As(err, &apiErr) && apiErr.Code == 403 {
// inspect permissions on destination bucket
}
return nil, fmt.Errorf("failed to copy %q/%q to %q/%q: %w", sourceBucket, sourceObject, destinationBucket, destinationObject, err)
} Defensive patterns
Strategy: validation
Validate before calling
// Go: verify source object and both buckets before CopyObject
func precheckCopy(ctx context.Context, src *cloudstorage.Source, sBucket, sObject, dBucket, dObject string) error {
if _, err := src.Client.Bucket(sBucket).Object(sObject).Attrs(ctx); err != nil {
return fmt.Errorf("source gs://%s/%s missing: %w", sBucket, sObject, err)
}
if _, err := src.Client.Bucket(dBucket).Attrs(ctx); err != nil {
return fmt.Errorf("destination bucket %q missing: %w", dBucket, err)
}
return nil
} Type guard
// Go: distinguish missing-object errors from permission errors
func copyFailureKind(err error) string {
if errors.Is(err, storage.ErrObjectNotExist) {
return "not-found"
}
var apiErr *googleapi.Error
if errors.As(err, &apiErr) {
if apiErr.Code == 403 || apiErr.Code == 401 {
return "permission"
}
if apiErr.Code == 404 {
return "bucket-not-found"
}
}
return "other"
} Try / catch
err := copyFailureKind(nil) // placeholder removal
_, err = src.CopyObject(ctx, sBucket, sObject, dBucket, dObject)
if err != nil {
switch copyFailureKind(err) {
case "not-found":
return fmt.Errorf("source object gs://%s/%s does not exist", sBucket, sObject)
case "permission", "bucket-not-found":
return fmt.Errorf("check IAM/bucket config: %w", err)
default:
// transient: safe to retry, copy overwrites destination atomically
}
} Prevention
- Always Attrs()-check the source object when its existence depends on prior pipeline steps.
- Encode object names correctly — spaces and unicode must be handled by the client, not hand-concatenated into paths.
- Grant storage.objects.get on source and storage.objects.create on destination to the same identity.
- For cross-region/project copies, validate org policies on the destination bucket first.
- Exploit copy atomicity: retries are safe; log the wrapped error to identify which side failed.
When it happens
Trigger: Calling CopyObject(sourceBucket, sourceObject, destinationBucket, destinationObject) when: the source object does not exist (storage.ErrObjectNotExist), the caller lacks storage.objects.get on source or storage.objects.create on destination, either bucket is absent/misnamed, the destination bucket is in a different location with conflicting constraints, or the copy exceeds limits / ctx is cancelled.
Common situations: Copying objects whose names contain unencoded special characters, moving data between buckets in different regions/projects with cross-project permission gaps, deleting-then-copying races in pipelines, typos in object paths, buckets rejected by org policy (CMEK, uniform bucket-level access).
Related errors
- failed to move %q to %q in bucket %q: %w
- failed to finalize upload of %q to %q/%q: %w
- failed to finalize write to %q/%q: %w
- failed to rename file: %w
- error writing script %s: %w
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/ab55cc91c4c584b7.
Report an issue: GitHub.