JuliusBrussee/caveman · error

objectstore: list versions

Error message

objectstore: list versions %q: %w

What it means

deleteVersions enumerates all versions of a key/prefix with ListObjects(WithVersions:true); this error wraps a per-entry obj.Err from that listing stream. The delete operation aborts before removing anything further because the version inventory itself could not be read. The key or prefix is embedded and the underlying error preserved with %w.

Solutions

  1. Grant s3:ListBucketVersions (and s3:GetObjectVersion) on the bucket for versioned listings.
  2. If the backend does not support version listing, note deleteVersions falls back to ordinary Delete only for exact keys — for prefixes you must delete directly.
  3. Retry the operation; version listing is paginated and transient failures are common on large prefixes.
  4. Check backend compatibility for WithVersions on S3-compatible stores.
  5. Inspect the wrapped error for throttling (SlowDown) and back off.

Example fix

// before
if err := store.DeleteAllVersions(ctx, key); err != nil {
    return err
}
// after
if err := store.DeleteAllVersions(ctx, key); err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return retryWithBackoff(ctx, func() error { return store.DeleteAllVersions(ctx, key) })
    }
    return fmt.Errorf("purge %s: %w", key, err)
}
Defensive patterns

Strategy: retry

Validate before calling

func canListVersions(ctx context.Context, c *minio.Client, bucket string) error {
    for obj := range c.ListObjects(ctx, bucket, minio.ListObjectsOptions{WithVersions: true, MaxKeys: 1}) {
        return obj.Err
    }
    return nil
}

Try / catch

if err := store.DeleteAllVersions(ctx, key); err != nil {
    var mr minio.ErrorResponse
    if errors.As(err, &mr) && mr.Code == "AccessDenied" {
        return fmt.Errorf("missing s3:ListBucketVersions: %w", err)
    }
    return retryWithBackoff(ctx, err)
}

Prevention

When it happens

Trigger: Calling DeleteAllVersions or DeletePrefixAllVersions when ListObjects with WithVersions:true yields an entry with obj.Err != nil: listing permission missing (s3:ListBucketVersions), interrupted stream, or backend error mid-pagination.

Common situations: Bucket with versioning enabled but IAM lacking ListBucketVersions; non-AWS S3-compatible backends (e.g. some MinIO versions, Ceph) returning errors on versioned listings; expired credentials mid-iteration; network drop during a long prefix listing.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/36a5cd62ae523056. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/objectstore/objectstore.go:327

	}
	return nil
}

func (m *minioStore) DeleteAllVersions(ctx context.Context, key string) error {
	return m.deleteVersions(ctx, key, true)
}

func (m *minioStore) DeletePrefixAllVersions(ctx context.Context, prefix string) error {
	return m.deleteVersions(ctx, prefix, false)
}

func (m *minioStore) deleteVersions(ctx context.Context, keyOrPrefix string, exact bool) error {
	found := false
	for obj := range m.client.ListObjects(ctx, m.bucket, minio.ListObjectsOptions{
		Prefix: keyOrPrefix, Recursive: true, WithVersions: true,
	}) {
		if obj.Err != nil {
			return fmt.Errorf("objectstore: list versions %q: %w", keyOrPrefix, obj.Err)
		}
		if exact && obj.Key != keyOrPrefix {
			continue
		}
		found = true
		if err := m.client.RemoveObject(ctx, m.bucket, obj.Key, minio.RemoveObjectOptions{VersionID: obj.VersionID}); err != nil {
			return fmt.Errorf("objectstore: delete version %q (%q): %w", obj.Key, obj.VersionID, err)
		}
	}
	if exact && !found {
		// Non-versioned/S3-compatible backends may omit version listings. Ordinary
		// deletion remains idempotent and covers that deployment shape.
		return m.Delete(ctx, keyOrPrefix)
	}
	return nil
}

func (m *minioStore) Exists(ctx context.Context, key string) (bool, error) {

View on GitHub (pinned to 3ee70a1026)