JuliusBrussee/caveman · error
objectstore: put
Error message
objectstore: put %q: %w
What it means
minioStore.Put wraps MinIO PutObject errors with 'objectstore: put %q: %w', naming the object key so the failing write is identifiable. The library throws it for any failure uploading the body: connectivity, auth, missing bucket, permissions, or size/policy limits.
Solutions
- Inspect the wrapped cause for the class: connection refused (endpoint/network), AccessDenied (IAM), NoSuchBucket (bucket), timeout (ctx).
- Confirm the bucket exists and credentials allow PutObject on it.
- Check network/DNS from the service to the endpoint and retry transient failures with backoff.
- Honor ctx deadlines — extend the timeout if legitimate uploads are being cut off.
Example fix
// before
if err := store.Put(ctx, key, body, ct); err != nil { return err } // wraps: AccessDenied
// after
if err := store.Put(ctx, key, body, ct); err != nil {
var resp minio.ErrorResponse
if errors.As(err, &resp) && resp.Code == "AccessDenied" {
return fmt.Errorf("grant s3:PutObject on bucket %q to the configured key", resp.BucketName)
}
return err
} Defensive patterns
Strategy: retry
Validate before calling
// before Put: ensure context has sane deadline ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()
Try / catch
err := store.Put(ctx, key, body, ct)
if err != nil {
var resp minio.ErrorResponse
if errors.As(err, &resp) {
switch resp.Code {
case "AccessDenied": // fix IAM
case "NoSuchBucket": // recreate bucket
default: // transient: retry with backoff
}
}
if ctx.Err() == context.DeadlineExceeded { /* extend timeout */ }
} Prevention
- Retry idempotent puts with exponential backoff for transient MinIO errors.
- Provision bucket and IAM (PutObject) in IaC before the app starts.
- Set realistic context timeouts for large bodies.
- Monitor wrapped causes (%w) and alert on AccessDenied/NoSuchBucket codes.
When it happens
Trigger: Calling Store.Put (or the Probe path) where m.client.PutObject fails: network partition to the endpoint, wrong credentials, bucket deleted, IAM lacking s3:PutObject, request exceeding bucket policy size limits, or context deadline exceeded.
Common situations: Bucket auto-removed in dev environments; expired/rotated static credentials; MinIO restarted or scaled down in k8s; DNS resolution failures; long uploads killed by ctx timeout.
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: get
- objectstore: production read/write/delete probe failed
- objectstore: read
- AbortError
- awscreds: read response
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/d9523e48c74703a5.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/objectstore/objectstore.go:278
cleaned = true
exists, err := store.Exists(ctx, key)
if err != nil {
return err
}
if exists {
return errors.New("probe object still exists after purge")
}
return nil
}
func (m *minioStore) Put(ctx context.Context, key string, body []byte, contentType string) error {
if contentType == "" {
contentType = "application/octet-stream"
}
_, err := m.client.PutObject(ctx, m.bucket, key, bytes.NewReader(body), int64(len(body)),
minio.PutObjectOptions{ContentType: contentType})
if err != nil {
return fmt.Errorf("objectstore: put %q: %w", key, err)
}
return nil
}
func (m *minioStore) Get(ctx context.Context, key string) ([]byte, error) {
obj, err := m.client.GetObject(ctx, m.bucket, key, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("objectstore: get %q: %w", key, err)
}
defer obj.Close()
maxBytes := int64(env.Int("CAVE_OBJECTSTORE_MAX_READ_BYTES", 64<<20))
if maxBytes <= 0 {
maxBytes = 64 << 20
}
body, err := io.ReadAll(io.LimitReader(obj, maxBytes+1))
if err != nil {
if minio.ToErrorResponse(err).Code == "NoSuchKey" {
return nil, ErrNotFoundView on GitHub (pinned to 3ee70a1026)