{"record":{"id":"93fb2ffafae042eb","repo":"grafana/k6","slug":"reading-request-body-w","errorCode":null,"errorMessage":"reading request body: %w","messagePattern":"reading request body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cloudapi/provisioning/http_client.go","lineNumber":43,"sourceCode":"\tversion    string // for User-Agent\n\tlogger     logrus.FieldLogger\n}\n\n// NewHTTPClient constructs an HTTPClient for metrics push and notify\n// in provisioning mode. version is used for the User-Agent header,\n// matching the v1/v6 client convention `k6cloud/<version>`.\nfunc NewHTTPClient(httpClient *http.Client, token, version string, logger logrus.FieldLogger) *HTTPClient {\n\treturn &HTTPClient{httpClient: httpClient, token: token, version: version, logger: logger}\n}\n\n// Do executes the request with Bearer auth, retries on 5xx and\n// transport errors, and decodes the response body into v if non-nil.\nfunc (p *HTTPClient) Do(req *http.Request, v any) error {\n\t// Ensure GetBody is set so the body can be replayed on retries.\n\tif req.Body != nil && req.GetBody == nil {\n\t\tbody, err := io.ReadAll(req.Body)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reading request body: %w\", err)\n\t\t}\n\t\t_ = req.Body.Close()\n\n\t\treq.GetBody = func() (io.ReadCloser, error) {\n\t\t\treturn io.NopCloser(bytes.NewReader(body)), nil\n\t\t}\n\t\treq.Body, _ = req.GetBody()\n\t\treq.ContentLength = int64(len(body))\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+p.token)\n\treq.Header.Set(\"User-Agent\", \"k6cloud/\"+p.version)\n\n\tresp, err := doWithRetry(p.httpClient, req)\n\tif err != nil {\n\t\treturn err\n\t}\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/grafana/k6/blob/01ffac6f245854c1b8adc6a69857c76a15f90022/internal/cloudapi/provisioning/http_client.go#L25-L61","documentation":"Returned by HTTPClient.Do (internal/cloudapi/provisioning/http_client.go:43) when io.ReadAll fails while buffering the caller's request body to install req.GetBody for retry replay. Note a merely already-consumed body is NOT an error here (ReadAll gets EOF, yields empty bytes); the error requires the underlying reader to actually return a non-EOF error mid-read. It happens before any network I/O and before auth headers are set.","triggerScenarios":"Passing an *http.Request whose Body reader errors when read: a closed net.Conn/pipe used as a body, a custom io.Reader returning an error after N bytes, or a body concurrently consumed/closed by another goroutine while Do is reading it.","commonSituations":"Streaming a body from a pipe (io.Pipe) whose writer errored or exited early; reusing a single *http.Request object across concurrent Do calls (double-read races); custom metered/limiting readers that surface limits as errors.","solutions":["Identify the concrete reader behind req.Body and what error it returns — the %w chain names it (e.g. io.ErrClosedPipe).","Buffer the payload into a []byte first and construct the request with bytes.NewReader so the body is always replayable and error-free.","Never share one *http.Request across goroutines; build one per Do call.","For pipe-based streaming, ensure the writer side completes without error before or while Do reads."],"exampleFix":"// before — erroring/stream body\nreq, _ := http.NewRequestWithContext(ctx, http.MethodPost, pushURL, pr) // pr is *io.PipeReader\nerr := httpClient.Do(req, &result)\n\n// after — buffer first so GetBody replay and reads cannot fail\nbuf, err := io.ReadAll(pr)\nif err != nil {\n\treturn fmt.Errorf(\"buffering payload: %w\", err)\n}\nreq, _ := http.NewRequestWithContext(ctx, http.MethodPost, pushURL, bytes.NewReader(buf))\nerr = httpClient.Do(req, &result)","handlingStrategy":"validation","validationCode":"// Materialize the body into memory so Do's buffering can never fail.\nfunc requestWithBufferedBody(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) {\n\tbuf, err := io.ReadAll(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"buffering request body: %w\", err)\n\t}\n\treturn http.NewRequestWithContext(ctx, method, urlStr, bytes.NewReader(buf))\n}","typeGuard":null,"tryCatchPattern":"if err := p.Do(req, &out); err != nil {\n\tif strings.Contains(err.Error(), \"reading request body\") {\n\t\t// the caller's body reader errored — fix the producer; do not retry the same req object\n\t\treturn fmt.Errorf(\"request body stream failed before send; buffer it first: %w\", err)\n\t}\n}","preventionTips":["Always pass replayable bodies (bytes.Reader/bytes.Buffer) to HTTPClient.Do.","Never reuse one *http.Request across concurrent Do calls.","For pipe-based bodies, ensure the writer side closes cleanly before Do reads."],"tags":["k6","cloud","http-client","request-body","io","retry-replay"],"backgroundTag":"request-body-read-failed","analyzedSha":"01ffac6f245854c1b8adc6a69857c76a15f90022","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}