{"record":{"id":"77c84b08e8bdb437","repo":"thanos-io/thanos","slug":"read-body","errorCode":null,"errorMessage":"read body","messagePattern":"read body","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/promclient/promclient.go","lineNumber":141,"sourceCode":"\t\treq.Header = headers\n\t}\n\n\tif c.userAgent != \"\" {\n\t\treq.Header.Set(\"User-Agent\", c.userAgent)\n\t}\n\tif method == http.MethodPost {\n\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t}\n\n\tresp, err := c.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn nil, 0, errors.Wrapf(err, \"perform %s request against %s\", method, u.String())\n\t}\n\tdefer runutil.ExhaustCloseWithErrCapture(&err, resp.Body, \"%s: close body\", req.URL.String())\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, resp.StatusCode, errors.Wrap(err, \"read body\")\n\t}\n\tif resp.StatusCode/100 != 2 {\n\t\treturn nil, resp.StatusCode, errors.Errorf(\"expected 2xx response, got %d. Body: %v\", resp.StatusCode, string(body))\n\t}\n\treturn body, resp.StatusCode, nil\n}\n\n// IsWALDirAccessible returns no error if WAL dir can be found. This helps to tell\n// if we have access to Prometheus TSDB directory.\nfunc IsWALDirAccessible(dir string) error {\n\tconst errMsg = \"WAL dir is not accessible. Is this dir a TSDB directory? If yes it is shared with TSDB?\"\n\n\tf, err := os.Stat(filepath.Join(dir, \"wal\"))\n\tif err != nil {\n\t\treturn errors.Wrap(err, errMsg)\n\t}\n\n\tif !f.IsDir() {","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/promclient/promclient.go#L123-L159","documentation":"req2xx successfully received an HTTP response but io.ReadAll(resp.Body) failed while reading it, wrapped as \"read body\". This means the connection broke or timed out mid-response, so the body is incomplete.","triggerScenarios":"Prometheus closes the connection mid-transfer (restart, LB idle timeout, proxy error); large query results truncated by network interruption; context deadline hits while streaming the body.","commonSituations":"Big QueryRange results over flaky networks; ingress/LB response timeouts shorter than query duration; Prometheus OOM-killed while serving a heavy response.","solutions":["Retry the request — mid-body failures are usually transient","Reduce response size (narrow the query time range, add step/limit)","Increase LB/proxy idle and read timeouts to exceed worst-case query duration","Check Prometheus and proxy logs for restarts or errors during the request window"],"exampleFix":"// before\nbody, err := client.QueryRange(ctx, apiCtx, ...)\n// after with retry\nerr := runutil.Retry(2*time.Second, ctx.Done(), func() error {\n    _, err = client.QueryRange(ctx, apiCtx, ...)\n    if err != nil && strings.Contains(err.Error(), \"read body\") {\n        return err // transient mid-body read failure: retry\n    }\n    return runutil.StopRetry(err)\n})","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"body, _, err := client.QueryInstant(ctx, apiCtx, nil)\nif err != nil && strings.Contains(err.Error(), \"read body\") {\n    // transient mid-response disconnect: retry with backoff\n    return retryWithBackoff(ctx)\n}","preventionTips":["Set LB/proxy read+idle timeouts above the slowest query duration","Keep queries bounded (time range, step, series limits) to shrink bodies","Retry transient body-read failures idempotently","Watch for Prometheus restarts correlated with request times"],"tags":["http","network","io"],"backgroundTag":"network-request-failed","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}