grafana/k6 · error

reading request body: %w

Error message

reading request body: %w

What it means

HTTPClient.Do (internal/cloudapi/provisioning/http_client.go:43) reads the request body into memory to install req.GetBody, which is required so the body can be replayed on 5xx/transport retries. This error means the caller-supplied body reader itself failed while being read — the request never went on the wire.

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 93accf6570)

Solutions

  1. Buffer the payload into []byte and build the request with bytes.NewReader so the body is trivially replayable
  2. Fix or check the underlying reader's error source (file handle, pipe writer, decompressor)
  3. Set req.GetBody yourself when the body is already replayable, which skips the buffering path entirely

Example fix

// before
req, _ := http.NewRequestWithContext(ctx, "POST", pushURL, failingStream)
err := client.Do(req, &resp)

// after
payload, err := io.ReadAll(failingStream)
if err != nil {
	return err
}
req, err := http.NewRequestWithContext(ctx, "POST", pushURL, bytes.NewReader(payload))
if err != nil {
	return err
}
err = client.Do(req, &resp)
Defensive patterns

Strategy: validation

Validate before calling

payload, err := io.ReadAll(body)
if err != nil {
	return fmt.Errorf("buffering request body: %w", err)
}
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(payload))

Type guard

func isReplayable(req *http.Request) bool {
	return req.Body == nil || req.GetBody != nil
}

Try / catch

if err := httpClient.Do(req, &out); err != nil {
	if strings.Contains(err.Error(), "reading request body") {
		// the body stream itself is broken; rebuild the request from a []byte buffer
	}
	return err
}

Prevention

When it happens

Trigger: An *http.Request built over a custom io.Reader that returns an error mid-read (a pipe whose writer closed, a failing decompression stream, a file that vanished), passed to Do with GetBody unset.

Common situations: Metrics-push or notify requests assembled over streaming/compressed readers instead of byte slices; test harnesses piping bodies through processes that exit early.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/93fb2ffafae042eb. Report an issue: GitHub.