JuliusBrussee/caveman · error

objectstore: list

Error message

objectstore: list %q: %w

What it means

minioStore.List wraps obj.Err produced while iterating ListObjects with the given prefix (recursive). The listing stream is lazy; errors arrive mid-iteration as per-entry errors, so a partially built keys slice is discarded and the prefix plus cause are wrapped with %w.

Solutions

  1. Unwrap and check minio.ErrorResponse for AccessDenied (grant s3:ListBucket) or SlowDown (backoff).
  2. Retry the whole List on transient errors; results are recomputed from scratch.
  3. Use ListPage with bounded maxKeys to reduce per-call work on huge prefixes.
  4. Check network stability/timeout configuration for long listings.
  5. Avoid extremely broad prefixes; narrow them to reduce pagination.

Example fix

// before
keys, err := store.List(ctx, "") // lists entire bucket
// after
keys, err := store.List(ctx, "uploads/2026/09/") // narrow prefix, fewer pages
if err != nil {
    return fmt.Errorf("list uploads: %w", err)
}
Defensive patterns

Strategy: retry

Try / catch

keys, err := store.List(ctx, prefix)
if err != nil {
    var mr minio.ErrorResponse
    if errors.As(err, &mr) && mr.Code == "SlowDown" {
        time.Sleep(backoff.Next())
        keys, err = store.List(ctx, prefix)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling List(ctx, prefix) when the recursive listing fails partway: AccessDenied on s3:ListBucket, connection drop during pagination, SlowDown throttling, or invalid prefix causing backend errors.

Common situations: Large prefixes that require many paginated LIST calls over unstable networks; IAM missing list permission; heavy concurrent listing triggering rate limits; S3-compatible backends with pagination quirks.

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/d6d2aad22dbf78a8. Report an issue: GitHub.

Appendix: source

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

	return nil
}

func (m *minioStore) Exists(ctx context.Context, key string) (bool, error) {
	_, err := m.client.StatObject(ctx, m.bucket, key, minio.StatObjectOptions{})
	if err != nil {
		if minio.ToErrorResponse(err).Code == "NoSuchKey" {
			return false, nil
		}
		return false, fmt.Errorf("objectstore: stat %q: %w", key, err)
	}
	return true, nil
}

func (m *minioStore) List(ctx context.Context, prefix string) ([]string, error) {
	var keys []string
	for obj := range m.client.ListObjects(ctx, m.bucket, minio.ListObjectsOptions{Prefix: prefix, Recursive: true}) {
		if obj.Err != nil {
			return nil, fmt.Errorf("objectstore: list %q: %w", prefix, obj.Err)
		}
		keys = append(keys, obj.Key)
	}
	return keys, nil
}

func (m *minioStore) ListPage(ctx context.Context, prefix, startAfter string, maxKeys int) (ObjectPage, error) {
	if maxKeys <= 0 {
		return ObjectPage{}, fmt.Errorf("objectstore: page size must be positive")
	}
	// Request one extra key so the caller can tell whether another page exists,
	// while cancelling the producer as soon as the bounded result is known.
	pageCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	keys := make([]string, 0, maxKeys)
	var extra string
	for obj := range m.client.ListObjects(pageCtx, m.bucket, minio.ListObjectsOptions{
		Prefix: prefix, Recursive: true, StartAfter: startAfter, MaxKeys: maxKeys + 1,

View on GitHub (pinned to 3ee70a1026)