JuliusBrussee/caveman · error

objectstore: stat

Error message

objectstore: stat %q: %w

What it means

minioStore.Exists wraps failures from client.StatObject when checking key presence. NoSuchKey is intentionally translated to (false, nil), so this error only appears for anything else: permissions, network problems, bucket mismatches, or backend errors. It keeps the key and underlying error via %w.

Solutions

  1. Unwrap and check minio.ErrorResponse.Code: NoSuchBucket -> fix bucket config; AccessDenied -> grant s3:GetObject.
  2. Verify the endpoint URL, region, and TLS settings in the store constructor.
  3. Add backoff if Exists is called in a tight polling loop (throttling).
  4. Retry once on transient network errors before surfacing.
  5. Ensure credentials/env (MINIO access/secret) are set for the deployment.

Example fix

// before
ok, err := store.Exists(ctx, key)
if err != nil {
    return err
}
// after
ok, err := store.Exists(ctx, key)
if err != nil {
    var mr minio.ErrorResponse
    if errors.As(err, &mr) && mr.Code == "NoSuchBucket" {
        return fmt.Errorf("bucket %q does not exist", mr.BucketName)
    }
    return fmt.Errorf("exists %s: %w", key, err)
}
Defensive patterns

Strategy: try-catch

Try / catch

ok, err := store.Exists(ctx, key)
if err != nil {
    var mr minio.ErrorResponse
    if errors.As(err, &mr) && (mr.Code == "NoSuchBucket" || mr.Code == "AccessDenied") {
        return fmt.Errorf("objectstore misconfigured (%s): %w", mr.Code, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Exists(ctx, key) when StatObject returns a non-NoSuchKey error: AccessDenied on the bucket, no such bucket, DNS/connection failure, TLS mismatch, or throttling.

Common situations: IAM policy granting List but not GetObject (stat needs read); wrong bucket name after config change; MinIO endpoint unreachable in local dev; request-rate throttling during hot polling loops.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

		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) {
	_, 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")

View on GitHub (pinned to 3ee70a1026)