{"record":{"id":"d9523e48c74703a5","repo":"JuliusBrussee/caveman","slug":"objectstore-put-q-w","errorCode":null,"errorMessage":"objectstore: put %q: %w","messagePattern":"objectstore: put %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/objectstore/objectstore.go","lineNumber":278,"sourceCode":"\tcleaned = true\n\texists, err := store.Exists(ctx, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif exists {\n\t\treturn errors.New(\"probe object still exists after purge\")\n\t}\n\treturn nil\n}\n\nfunc (m *minioStore) Put(ctx context.Context, key string, body []byte, contentType string) error {\n\tif contentType == \"\" {\n\t\tcontentType = \"application/octet-stream\"\n\t}\n\t_, err := m.client.PutObject(ctx, m.bucket, key, bytes.NewReader(body), int64(len(body)),\n\t\tminio.PutObjectOptions{ContentType: contentType})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"objectstore: put %q: %w\", key, err)\n\t}\n\treturn nil\n}\n\nfunc (m *minioStore) Get(ctx context.Context, key string) ([]byte, error) {\n\tobj, err := m.client.GetObject(ctx, m.bucket, key, minio.GetObjectOptions{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"objectstore: get %q: %w\", key, err)\n\t}\n\tdefer obj.Close()\n\tmaxBytes := int64(env.Int(\"CAVE_OBJECTSTORE_MAX_READ_BYTES\", 64<<20))\n\tif maxBytes <= 0 {\n\t\tmaxBytes = 64 << 20\n\t}\n\tbody, err := io.ReadAll(io.LimitReader(obj, maxBytes+1))\n\tif err != nil {\n\t\tif minio.ToErrorResponse(err).Code == \"NoSuchKey\" {\n\t\t\treturn nil, ErrNotFound","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/objectstore/objectstore.go#L260-L296","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := store.Put(ctx, key, body, ct); err != nil { return err } // wraps: AccessDenied\n// after\nif err := store.Put(ctx, key, body, ct); err != nil {\n    var resp minio.ErrorResponse\n    if errors.As(err, &resp) && resp.Code == \"AccessDenied\" {\n        return fmt.Errorf(\"grant s3:PutObject on bucket %q to the configured key\", resp.BucketName)\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":"// before Put: ensure context has sane deadline\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()","typeGuard":null,"tryCatchPattern":"err := store.Put(ctx, key, body, ct)\nif err != nil {\n    var resp minio.ErrorResponse\n    if errors.As(err, &resp) {\n        switch resp.Code {\n        case \"AccessDenied\": // fix IAM\n        case \"NoSuchBucket\": // recreate bucket\n        default: // transient: retry with backoff\n        }\n    }\n    if ctx.Err() == context.DeadlineExceeded { /* extend timeout */ }\n}","preventionTips":["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."],"tags":["minio","s3","upload","network"],"backgroundTag":"http-error-response","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}