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
- Unwrap and check minio.ErrorResponse.Code: NoSuchBucket -> fix bucket config; AccessDenied -> grant s3:GetObject.
- Verify the endpoint URL, region, and TLS settings in the store constructor.
- Add backoff if Exists is called in a tight polling loop (throttling).
- Retry once on transient network errors before surfacing.
- 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
- Ensure read (s3:GetObject) not just list permission for existence checks.
- Avoid hot Exists polling loops; use backoff or events.
- Validate bucket name/endpoint at startup with a canary StatObject.
- Never treat a non-NoSuchKey error as 'object missing'.
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
- objectstore: delete
- objectstore: delete version
- objectstore: list page
- objectstore: list
- objectstore: list versions
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)