googleapis/mcp-toolbox · error

failed to open object %q in bucket %q: %w

Error message

failed to open object %q in bucket %q: %w

What it means

NewRangeReader failed when opening the requested object (or byte range) for reading. The source wraps the GCS client error, so the underlying cause (404 not found, 403 denied, network failure) is preserved via %w.

Source

Thrown at internal/sources/cloudstorage/cloudstorage.go:254

// content, its content type, and the number of bytes read. offset and length
// follow storage.ObjectHandle.NewRangeReader semantics: length == -1 means
// "read to end of object"; a negative offset means "suffix from end" (in
// which case length must be -1). Reads larger than defaultMaxReadBytes are
// rejected with cloudstoragecommon.ErrReadSizeLimitExceeded so the caller can
// narrow the range. Objects whose bytes are not valid UTF-8 are rejected
// with cloudstoragecommon.ErrBinaryContent.
//
// TODO: MCP tool results only carry text today, so we gate this tool on
// utf8.Valid. When the toolbox supports non-text MCP content (embedded
// resources, images, blobs), expand this to detect content type and return
// binary payloads natively.
func (s *Source) ReadObject(ctx context.Context, bucket, object string, offset, length int64) (map[string]any, error) {
	if err := s.validateBucket(bucket); err != nil {
		return nil, err
	}
	reader, err := s.client.Bucket(bucket).Object(object).NewRangeReader(ctx, offset, length)
	if err != nil {
		return nil, fmt.Errorf("failed to open object %q in bucket %q: %w", object, bucket, err)
	}
	defer reader.Close()

	if remain := reader.Remain(); remain > defaultMaxReadBytes {
		return nil, fmt.Errorf("object %q: %d bytes exceeds %d byte limit: %w",
			object, remain, defaultMaxReadBytes,
			cloudstoragecommon.ErrReadSizeLimitExceeded)
	}

	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, fmt.Errorf("failed to read object %q in bucket %q: %w", object, bucket, err)
	}

	if !utf8.Valid(data) {
		return nil, fmt.Errorf("object %q in bucket %q: %w", object, bucket,
			cloudstoragecommon.ErrBinaryContent)
	}

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Check the wrapped error status: verify the object exists via ListObjects or the GCS console before reading.
  2. Grant the caller storage.objects.get permission (roles/storage.objectViewer) on the bucket/object.
  3. Clamp offset/length to the object's size (from object metadata) before calling ReadObject.
  4. Retry with backoff on transient network/5xx errors; check CMEK key access if the object is encrypted.

Example fix

// before
res, err := source.ReadObject(ctx, "bkt", "reports/2026-q3.csv", 0, -1) // object may not exist
// after
objs, _ := source.ListObjects(ctx, "bkt", "reports/", 100, "")
if !contains(objs, "reports/2026-q3.csv") { return fmt.Errorf("object missing") }
res, err := source.ReadObject(ctx, "bkt", "reports/2026-q3.csv", 0, -1)
Defensive patterns

Strategy: try-catch

Validate before calling

objs, err := source.ListObjects(ctx, bucket, object, 1, "")
if err != nil || len(objs.Objects) == 0 {
    return fmt.Errorf("object %s/%s does not exist", bucket, object)
}

Try / catch

var e *apierror.APIError
if errors.As(err, &e) && e.HTTPCode() == 404 {
    // object missing: inform user or check name
} else if e != nil && e.HTTPCode() == 403 {
    // fix IAM
} else {
    // retry with backoff
}

Prevention

When it happens

Trigger: ReadObject called with a bucket/object pair that does not exist, an offset/length outside the object (e.g. offset beyond object size returns ErrObjectNotExist or empty range error), or the caller lacks storage.objects.get IAM permission; also network errors during the initial HTTP request.

Common situations: Typo in object name or missing trailing/extra path segment; object deleted by a lifecycle rule; reading with an offset larger than the object; service account without objectViewer; objects requiring requester-pays or CMEK keys the caller cannot use.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/760a980750ccb61a. Report an issue: GitHub.