{"record":{"id":"ab55cc91c4c584b7","repo":"googleapis/mcp-toolbox","slug":"failed-to-copy-q-q-to-q-q-w","errorCode":null,"errorMessage":"failed to copy %q/%q to %q/%q: %w","messagePattern":"failed to copy %q/%q to %q/%q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/cloudstorage/cloudstorage.go","lineNumber":556,"sourceCode":"\t}, nil\n}\n\n// CopyObject copies an object to a destination object. The destination may be\n// in the same bucket or a different bucket. Existing destination objects are\n// replaced, matching Cloud Storage's copy semantics without preconditions.\nfunc (s *Source) CopyObject(ctx context.Context, sourceBucket, sourceObject, destinationBucket, destinationObject string) (map[string]any, error) {\n\tif err := s.validateBucket(sourceBucket); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := s.validateBucket(destinationBucket); err != nil {\n\t\treturn nil, err\n\t}\n\tsrc := s.client.Bucket(sourceBucket).Object(sourceObject)\n\tdst := s.client.Bucket(destinationBucket).Object(destinationObject)\n\n\tattrs, err := dst.CopierFrom(src).Run(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to copy %q/%q to %q/%q: %w\", sourceBucket, sourceObject, destinationBucket, destinationObject, err)\n\t}\n\n\treturn map[string]any{\n\t\t\"sourceBucket\":      sourceBucket,\n\t\t\"sourceObject\":      sourceObject,\n\t\t\"destinationBucket\": destinationBucket,\n\t\t\"destinationObject\": destinationObject,\n\t\t\"bytes\":             attrs.Size,\n\t\t\"contentType\":       attrs.ContentType,\n\t}, nil\n}\n\n// MoveObject atomically renames or moves an object within the same bucket using\n// Cloud Storage's native move API. Cross-bucket moves should be modeled as\n// CopyObject followed by DeleteObject.\nfunc (s *Source) MoveObject(ctx context.Context, bucket, sourceObject, destinationObject string) (map[string]any, error) {\n\tif err := s.validateBucket(bucket); err != nil {\n\t\treturn nil, err","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/cloudstorage/cloudstorage.go#L538-L574","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before: assume source exists\nattrs, err := dst.CopierFrom(src).Run(ctx)\n// after: pre-check source and classify errors\nif _, err := s.client.Bucket(sourceBucket).Object(sourceObject).Attrs(ctx); err != nil {\n    return nil, fmt.Errorf(\"source %q/%q not found: %w\", sourceBucket, sourceObject, err)\n}\nattrs, err := dst.CopierFrom(src).Run(ctx)\nif err != nil {\n    var apiErr *googleapi.Error\n    if errors.As(err, &apiErr) && apiErr.Code == 403 {\n        // inspect permissions on destination bucket\n    }\n    return nil, fmt.Errorf(\"failed to copy %q/%q to %q/%q: %w\", sourceBucket, sourceObject, destinationBucket, destinationObject, err)\n}","handlingStrategy":"validation","validationCode":"// Go: verify source object and both buckets before CopyObject\nfunc precheckCopy(ctx context.Context, src *cloudstorage.Source, sBucket, sObject, dBucket, dObject string) error {\n    if _, err := src.Client.Bucket(sBucket).Object(sObject).Attrs(ctx); err != nil {\n        return fmt.Errorf(\"source gs://%s/%s missing: %w\", sBucket, sObject, err)\n    }\n    if _, err := src.Client.Bucket(dBucket).Attrs(ctx); err != nil {\n        return fmt.Errorf(\"destination bucket %q missing: %w\", dBucket, err)\n    }\n    return nil\n}","typeGuard":"// Go: distinguish missing-object errors from permission errors\nfunc copyFailureKind(err error) string {\n    if errors.Is(err, storage.ErrObjectNotExist) {\n        return \"not-found\"\n    }\n    var apiErr *googleapi.Error\n    if errors.As(err, &apiErr) {\n        if apiErr.Code == 403 || apiErr.Code == 401 {\n            return \"permission\"\n        }\n        if apiErr.Code == 404 {\n            return \"bucket-not-found\"\n        }\n    }\n    return \"other\"\n}","tryCatchPattern":"err := copyFailureKind(nil) // placeholder removal\n_, err = src.CopyObject(ctx, sBucket, sObject, dBucket, dObject)\nif err != nil {\n    switch copyFailureKind(err) {\n    case \"not-found\":\n        return fmt.Errorf(\"source object gs://%s/%s does not exist\", sBucket, sObject)\n    case \"permission\", \"bucket-not-found\":\n        return fmt.Errorf(\"check IAM/bucket config: %w\", err)\n    default:\n        // transient: safe to retry, copy overwrites destination atomically\n    }\n}","preventionTips":["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."],"tags":["gcs","copy","permissions","object-not-found"],"backgroundTag":"gcs-object-copy-failed","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}