grafana/k6 · error
reading request body
Error message
reading request body: %w
What it means
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.
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.
Example fix
// before — erroring/stream body
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, pushURL, pr) // pr is *io.PipeReader
err := httpClient.Do(req, &result)
// after — buffer first so GetBody replay and reads cannot fail
buf, err := io.ReadAll(pr)
if err != nil {
return fmt.Errorf("buffering payload: %w", err)
}
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, pushURL, bytes.NewReader(buf))
err = httpClient.Do(req, &result) Defensive patterns
Strategy: validation
Validate before calling
// Materialize the body into memory so Do's buffering can never fail.
func requestWithBufferedBody(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) {
buf, err := io.ReadAll(body)
if err != nil {
return nil, fmt.Errorf("buffering request body: %w", err)
}
return http.NewRequestWithContext(ctx, method, urlStr, bytes.NewReader(buf))
} Try / catch
if err := p.Do(req, &out); err != nil {
if strings.Contains(err.Error(), "reading request body") {
// the caller's body reader errored — fix the producer; do not retry the same req object
return fmt.Errorf("request body stream failed before send; buffer it first: %w", err)
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18).
Data as JSON: /api/errors/93fb2ffafae042eb.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cloudapi/provisioning/http_client.go:43
version string // for User-Agent
logger logrus.FieldLogger
}
// NewHTTPClient constructs an HTTPClient for metrics push and notify
// in provisioning mode. version is used for the User-Agent header,
// matching the v1/v6 client convention `k6cloud/<version>`.
func NewHTTPClient(httpClient *http.Client, token, version string, logger logrus.FieldLogger) *HTTPClient {
return &HTTPClient{httpClient: httpClient, token: token, version: version, logger: logger}
}
// Do executes the request with Bearer auth, retries on 5xx and
// transport errors, and decodes the response body into v if non-nil.
func (p *HTTPClient) Do(req *http.Request, v any) error {
// Ensure GetBody is set so the body can be replayed on retries.
if req.Body != nil && req.GetBody == nil {
body, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("reading request body: %w", err)
}
_ = req.Body.Close()
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(body)), nil
}
req.Body, _ = req.GetBody()
req.ContentLength = int64(len(body))
}
req.Header.Set("Authorization", "Bearer "+p.token)
req.Header.Set("User-Agent", "k6cloud/"+p.version)
resp, err := doWithRetry(p.httpClient, req)
if err != nil {
return err
}
View on GitHub (pinned to 01ffac6f24)