googleapis/mcp-toolbox · error
failed to move %q to %q in bucket %q: %w
Error message
failed to move %q to %q in bucket %q: %w
What it means
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.
Source
Thrown at internal/sources/cloudstorage/cloudstorage.go:578
"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, err
}
attrs, err := s.client.Bucket(bucket).Object(sourceObject).Move(ctx, storage.MoveObjectDestination{Object: destinationObject})
if err != nil {
return nil, fmt.Errorf("failed to move %q to %q in bucket %q: %w", sourceObject, destinationObject, bucket, err)
}
return map[string]any{
"bucket": bucket,
"sourceObject": sourceObject,
"destinationObject": destinationObject,
"bytes": attrs.Size,
"contentType": attrs.ContentType,
}, nil
}
// DeleteObject deletes a GCS object.
func (s *Source) DeleteObject(ctx context.Context, bucket, object string) (map[string]any, error) {
if err := s.validateBucket(bucket); err != nil {
return nil, err
}
if err := s.client.Bucket(bucket).Object(object).Delete(ctx); err != nil {
return nil, fmt.Errorf("failed to delete object %q in bucket %q: %w", object, bucket, err)View on GitHub (pinned to 8cc6e09de2)
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.
Example fix
// before: unconditional native move
attrs, err := obj.Move(ctx, storage.MoveObjectDestination{Object: destinationObject})
// after: fallback when the backend doesn't support Move
if _, aerr := obj.Attrs(ctx); aerr != nil {
return nil, fmt.Errorf("source %q not found: %w", sourceObject, aerr)
}
attrs, err := obj.Move(ctx, storage.MoveObjectDestination{Object: destinationObject})
if err != nil {
var apiErr *googleapi.Error
if errors.As(err, &apiErr) && apiErr.Code == http.StatusNotImplemented {
return s.copyThenDelete(ctx, bucket, sourceObject, destinationObject)
}
return nil, fmt.Errorf("failed to move %q to %q in bucket %q: %w", sourceObject, destinationObject, bucket, err)
} Defensive patterns
Strategy: fallback
Validate before calling
// Go: confirm source exists and destination is free before MoveObject
func precheckMove(ctx context.Context, src *cloudstorage.Source, bucket, sourceObject, destinationObject string) error {
if _, err := src.Client.Bucket(bucket).Object(sourceObject).Attrs(ctx); err != nil {
return fmt.Errorf("source gs://%s/%s missing: %w", bucket, sourceObject, err)
}
if _, err := src.Client.Bucket(bucket).Object(destinationObject).Attrs(ctx); err == nil {
return fmt.Errorf("destination %q already exists", destinationObject)
}
return nil
} Type guard
// Go: detect unsupported-Move backends (emulators)
func isMoveUnsupported(err error) bool {
var apiErr *googleapi.Error
return errors.As(err, &apiErr) && (apiErr.Code == 501 || apiErr.Code == 400)
} Try / catch
_, err := src.MoveObject(ctx, bucket, sourceObject, destinationObject)
if err != nil {
if isMoveUnsupported(err) {
// fallback: copy then delete (emulators / older API versions)
if _, cerr := src.CopyObject(ctx, bucket, sourceObject, bucket, destinationObject); cerr != nil {
return fmt.Errorf("move fallback copy failed: %w", cerr)
}
if _, derr := src.DeleteObject(ctx, bucket, sourceObject); derr != nil {
return fmt.Errorf("move fallback delete failed: %w", derr)
}
return nil
}
return fmt.Errorf("move failed: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to copy %q/%q to %q/%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/f8da6d9093fe026c.
Report an issue: GitHub.