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

  1. Identify the concrete reader behind req.Body and what error it returns — the %w chain names it (e.g. io.ErrClosedPipe).
  2. Buffer the payload into a []byte first and construct the request with bytes.NewReader so the body is always replayable and error-free.
  3. Never share one *http.Request across goroutines; build one per Do call.
  4. 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

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)