{"record":{"id":"f8da6d9093fe026c","repo":"googleapis/mcp-toolbox","slug":"failed-to-move-q-to-q-in-bucket-q-w","errorCode":null,"errorMessage":"failed to move %q to %q in bucket %q: %w","messagePattern":"failed to move %q to %q in bucket %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/cloudstorage/cloudstorage.go","lineNumber":578,"sourceCode":"\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\n\t}\n\tattrs, err := s.client.Bucket(bucket).Object(sourceObject).Move(ctx, storage.MoveObjectDestination{Object: destinationObject})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to move %q to %q in bucket %q: %w\", sourceObject, destinationObject, bucket, err)\n\t}\n\n\treturn map[string]any{\n\t\t\"bucket\":            bucket,\n\t\t\"sourceObject\":      sourceObject,\n\t\t\"destinationObject\": destinationObject,\n\t\t\"bytes\":             attrs.Size,\n\t\t\"contentType\":       attrs.ContentType,\n\t}, nil\n}\n\n// DeleteObject deletes a GCS object.\nfunc (s *Source) DeleteObject(ctx context.Context, bucket, object string) (map[string]any, error) {\n\tif err := s.validateBucket(bucket); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := s.client.Bucket(bucket).Object(object).Delete(ctx); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to delete object %q in bucket %q: %w\", object, bucket, err)","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/cloudstorage/cloudstorage.go#L560-L596","documentation":"MoveObject uses Cloud Storage's native ObjectHandle.Move API to atomically rename an object within a single bucket. This error wraps any failure of that call: the object is left untouched on failure (atomic). Notably, the Move API is not supported by all emulators/backends (e.g. fake-gcs-server), and it requires both read on the source and create/delete on the destination name.","triggerScenarios":"Calling MoveObject(bucket, sourceObject, destinationObject) when: the source object does not exist, the destination name already exists (depending on API semantics/preconditions) or is invalid, the caller lacks storage.objects.delete/create permissions, the bucket uses a backend without Move support (emulators, older API versions), or ctx is cancelled.","commonSituations":"Test suites using fake-gcs-server or the GCS emulator that don't implement the move API, pipelines racing on rename where the source was already moved, IAM roles granting create but not delete, cross-bucket moves mistakenly passed to MoveObject instead of CopyObject+Delete.","solutions":["Check errors.Is(err, storage.ErrObjectNotExist): verify the source object exists via ObjectHandle.Attrs before moving.","If running against an emulator (fake-gcs-server etc.) that lacks the Move API, fall back to CopyObject followed by DeleteObject.","Grant storage.objects.create and storage.objects.delete on the bucket to the credentials.","Ensure the move is same-bucket; for cross-bucket moves use CopyObject then DeleteObject explicitly.","Retry on 429/5xx with backoff; the atomic move makes retries safe, but handle 'already moved' by checking destination attrs."],"exampleFix":"// before: unconditional native move\nattrs, err := obj.Move(ctx, storage.MoveObjectDestination{Object: destinationObject})\n// after: fallback when the backend doesn't support Move\nif _, aerr := obj.Attrs(ctx); aerr != nil {\n    return nil, fmt.Errorf(\"source %q not found: %w\", sourceObject, aerr)\n}\nattrs, err := obj.Move(ctx, storage.MoveObjectDestination{Object: destinationObject})\nif err != nil {\n    var apiErr *googleapi.Error\n    if errors.As(err, &apiErr) && apiErr.Code == http.StatusNotImplemented {\n        return s.copyThenDelete(ctx, bucket, sourceObject, destinationObject)\n    }\n    return nil, fmt.Errorf(\"failed to move %q to %q in bucket %q: %w\", sourceObject, destinationObject, bucket, err)\n}","handlingStrategy":"fallback","validationCode":"// Go: confirm source exists and destination is free before MoveObject\nfunc precheckMove(ctx context.Context, src *cloudstorage.Source, bucket, sourceObject, destinationObject string) error {\n    if _, err := src.Client.Bucket(bucket).Object(sourceObject).Attrs(ctx); err != nil {\n        return fmt.Errorf(\"source gs://%s/%s missing: %w\", bucket, sourceObject, err)\n    }\n    if _, err := src.Client.Bucket(bucket).Object(destinationObject).Attrs(ctx); err == nil {\n        return fmt.Errorf(\"destination %q already exists\", destinationObject)\n    }\n    return nil\n}","typeGuard":"// Go: detect unsupported-Move backends (emulators)\nfunc isMoveUnsupported(err error) bool {\n    var apiErr *googleapi.Error\n    return errors.As(err, &apiErr) && (apiErr.Code == 501 || apiErr.Code == 400)\n}","tryCatchPattern":"_, err := src.MoveObject(ctx, bucket, sourceObject, destinationObject)\nif err != nil {\n    if isMoveUnsupported(err) {\n        // fallback: copy then delete (emulators / older API versions)\n        if _, cerr := src.CopyObject(ctx, bucket, sourceObject, bucket, destinationObject); cerr != nil {\n            return fmt.Errorf(\"move fallback copy failed: %w\", cerr)\n        }\n        if _, derr := src.DeleteObject(ctx, bucket, sourceObject); derr != nil {\n            return fmt.Errorf(\"move fallback delete failed: %w\", derr)\n        }\n        return nil\n    }\n    return fmt.Errorf(\"move failed: %w\", err)\n}","preventionTips":["Never use MoveObject across buckets — it is same-bucket only; use CopyObject + DeleteObject instead.","In test environments, verify your emulator supports the native Move API or wire in the copy+delete fallback from day one.","Check source existence before moving in pipelines where producers/consumers race.","Grant storage.objects.create AND storage.objects.delete on the bucket — move needs both.","Treat 'destination already exists' errors explicitly; decide overwrite policy up front."],"tags":["gcs","move","object-not-found","permissions","emulator"],"backgroundTag":"gcs-object-move-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"}