{"record":{"id":"575af4404f9b1f39","repo":"JuliusBrussee/caveman","slug":"objectstore-read-q-w","errorCode":null,"errorMessage":"objectstore: read %q: %w","messagePattern":"objectstore: read %q: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/objectstore/objectstore.go","lineNumber":298,"sourceCode":"\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\n\t\t}\n\t\treturn nil, fmt.Errorf(\"objectstore: read %q: %w\", key, err)\n\t}\n\tif int64(len(body)) > maxBytes {\n\t\treturn nil, ErrTooLarge\n\t}\n\treturn body, nil\n}\n\nfunc (m *minioStore) Delete(ctx context.Context, key string) error {\n\tif err := m.client.RemoveObject(ctx, m.bucket, key, minio.RemoveObjectOptions{}); err != nil {\n\t\treturn fmt.Errorf(\"objectstore: delete %q: %w\", key, err)\n\t}\n\treturn nil\n}\n\nfunc (m *minioStore) DeleteAllVersions(ctx context.Context, key string) error {\n\treturn m.deleteVersions(ctx, key, true)\n}\n","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/objectstore/objectstore.go#L280-L316","documentation":"minioStore.Get wraps a failure from io.ReadAll while streaming the object body from MinIO/S3. The error surfaces only when the underlying read of the response stream fails mid-transfer (after a successful StatObject/fetch); 'NoSuchKey' responses are already translated to the sentinel ErrNotFound, and oversized payloads become ErrTooLarge, so anything wrapped here is a genuine transport/IO problem. It preserves the object key and the underlying error via %w.","triggerScenarios":"Calling Get(ctx, key, maxBytes) on a minioStore when the HTTP body read from the object store fails: connection reset mid-stream, TLS errors, timeouts during transfer, or proxy/network interruption after the object was found but before the body was fully read.","commonSituations":"Flaky networks between app and MinIO; large objects over slow links hitting request deadlines; MinIO gateway to an unhealthy backend; load balancer idle-timeout closing the connection mid-body; expired credentials causing mid-stream auth failures on some proxies.","solutions":["Retry the Get with a bounded backoff — a single transient network hiccup is the most common cause.","Inspect the wrapped error (errors.Unwrap / minio.ToErrorResponse) to check for timeout vs. connection reset vs. TLS.","Verify network path: LB/proxy idle timeouts, keep-alive settings between client and MinIO endpoint.","Confirm TLS configuration matches the endpoint (scheme http vs https, CA trust).","Reduce object size or maxBytes if very large bodies consistently fail, and stream instead of ReadAll."],"exampleFix":"// before\nbody, err := store.Get(ctx, key, 10<<20)\nif err != nil {\n    return err\n}\n// after\nbody, err := store.Get(ctx, key, 10<<20)\nif err != nil {\n    if errors.Is(err, objectstore.ErrNotFound) || errors.Is(err, objectstore.ErrTooLarge) {\n        return err\n    }\n    // transient read failure: retry once\n    body, err = store.Get(ctx, key, 10<<20)\n    if err != nil {\n        return fmt.Errorf(\"get %s: %w\", key, err)\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := store.Get(ctx, key, max); err != nil && !errors.Is(err, objectstore.ErrNotFound) && !errors.Is(err, objectstore.ErrTooLarge) {\n    return retryOnTransient(ctx, func() error { _, err = store.Get(ctx, key, max); return err })\n}","preventionTips":["Keep request timeouts generous enough for maxBytes-sized objects.","Distinguish sentinel errors (ErrNotFound, ErrTooLarge) from transport failures before retrying.","Monitor connection-reset rates between app and MinIO; they indicate LB/keep-alive issues.","Use keep-alive and sane idle timeouts on the MinIO client transport."],"tags":["object-storage","network","io","minio"],"backgroundTag":"network-request-failed","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"}