GoogleContainerTools/skaffold · error

failed to read object: %v

Error message

failed to read object: %v

What it means

DownloadObject wraps errors from creating a GCS object reader (bucket.Object(uri).NewReader). It means the object could not be opened for download — most commonly it does not exist, the caller lacks read permission, or the request failed at the API/network level. The download aborts before any local file is written.

Source

Thrown at pkg/skaffold/gcs/client/native.go:326

		if err == iterator.Done {
			break
		}

		if err != nil {
			return nil, fmt.Errorf("failed to iterate objects: %v", err)
		}

		if attrs.Name != "" {
			matches = append(matches, attrs.Name)
		}
	}
	return matches, nil
}

func (nb nativeBucketHandler) DownloadObject(ctx context.Context, localPath, uri string) error {
	reader, err := nb.bucket.Object(uri).NewReader(ctx)
	if err != nil {
		return fmt.Errorf("failed to read object: %v", err)
	}
	defer reader.Close()

	file, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("failed to create file: %v", err)
	}
	defer file.Close()

	if _, err := io.Copy(file, reader); err != nil {
		return fmt.Errorf("failed to copy object to file: %v", err)
	}

	return nil
}

func (nb nativeBucketHandler) UploadObject(ctx context.Context, objName string, content *os.File) error {
	wc := nb.bucket.Object(objName).NewWriter(ctx)

View on GitHub (pinned to a1189de023)

Solutions

  1. Read the wrapped error: 404 means the object vanished or the name is wrong — re-list and retry
  2. Grant the identity roles/storage.objectViewer (storage.objects.get) on the bucket
  3. Retry the DownloadRecursive call on transient 5xx errors
  4. Check requester-pays configuration and set the billing user project if applicable

Example fix

// before
bucket.DownloadObject(ctx, localPath, uri) // 404: object deleted mid-download
// after
err := bucket.DownloadObject(ctx, localPath, uri)
if err != nil {
    if strings.Contains(err.Error(), "object doesn't exist") {
        err = retryWithBackoff(func() error { return bucket.DownloadObject(ctx, localPath, uri) })
    }
}
Defensive patterns

Strategy: retry

Validate before calling

func checkObjectReadable(ctx context.Context, bucketName, objName string) error {
	client, err := storage.NewClient(ctx)
	if err != nil { return err }
	defer client.Close()
	_, err = client.Bucket(bucketName).Object(objName).Attrs(ctx)
	return err // pre-flight existence/permission probe
}
// optionally probe before downloads, but prefer retry for race-prone listings

Try / catch

if err := n.DownloadRecursive(ctx, src, dst); err != nil {
	if strings.Contains(err.Error(), "failed to read object") {
		if isNotFound(err) || isTransient(err) {
			return retryBackoff(3, time.Second, func() error {
				return n.DownloadRecursive(ctx, src, dst)
			})
		}
	}
	return err
}

Prevention

When it happens

Trigger: During DownloadRecursive, an object listed earlier was deleted before download (race), the URI encodes an object the account cannot read, or the storage API returns 404/403/5xx when opening the reader.

Common situations: CI race where another job overwrites/deletes objects while downloading; bucket with uniform bucket-level access and a service account lacking storage.objects.get; eventual-consistency confusion after a fresh upload; requester-pays bucket without a billing project.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/a06e7365575a4b2c. Report an issue: GitHub.