grafana/k6 · error
decoding response
Error message
decoding response: %w
What it means
Returned by HTTPClient.Do (internal/cloudapi/provisioning/http_client.go:73) when the 2xx response body cannot be JSON-decoded into the caller's target v (EOF is tolerated, so empty 2xx bodies are fine). It fires after CheckResponse has already validated the status, so the server answered 'OK' but its body is not the JSON the client expected: malformed JSON, truncated body, or a shape mismatch (json.UnmarshalTypeError, e.g. a string where a number is declared).
Solutions
- Classify via the wrapped error: *json.SyntaxError → body isn't valid JSON (capture it); *json.UnmarshalTypeError → your struct's types don't match the payload.
- Log or dump the raw response body on failure (temporarily wrap resp.Body with a TeeReader-style capture) to see whether it is HTML/proxy output or truncated JSON.
- For proxy interference, bypass it for the cloud host (NO_PROXY) or fix TLS/interception config.
- For type mismatches, align the decode target with the current cloud API schema (update k6 / the SDK types).
- Treat truncation-from-network as transient: retry the push.
Example fix
// before
var result v6.Response
if err := httpClient.Do(req, &result); err != nil {
return err // "decoding response: invalid character '<' ..."
}
// after — distinguish the two decode failure classes
if err := httpClient.Do(req, &result); err != nil {
var typeErr *json.UnmarshalTypeError
switch {
case errors.As(err, &typeErr):
return fmt.Errorf("response field %q has type %s, expected %s — schema mismatch: %w", typeErr.Field, typeErr.Value, typeErr.Type.String(), err)
case strings.Contains(err.Error(), "invalid character"):
return fmt.Errorf("response is not JSON (proxy or error page?) — inspect body: %w", err)
}
return err
} Defensive patterns
Strategy: try-catch
Try / catch
if err := p.Do(req, &out); err != nil {
var typeErr *json.UnmarshalTypeError
var synErr *json.SyntaxError
switch {
case errors.As(err, &typeErr):
// schema drift — update decode target; not transient
return fmt.Errorf("response schema mismatch at field %q: %w", typeErr.Field, err)
case errors.As(err, &synErr):
// body not JSON (proxy/error page) or truncated — capture and re-raise with context
return fmt.Errorf("malformed JSON response (offset %d): %w", synErr.Offset, err)
}
return err
} Prevention
- Keep k6 (and its cloud SDK types) updated so decode targets match the deployed API schema.
- Configure NO_PROXY for cloud hosts when a corporate proxy rewrites responses.
- Log a body snippet on decode failure once — it immediately distinguishes HTML proxies from truncation.
When it happens
Trigger: Metrics-push or notify endpoints answering 2xx with non-JSON (HTML error page injected by a proxy), truncated responses from connection resets mid-body, or schema drift where the API's JSON field types no longer match the Go decode target.
Common situations: Corporate proxies/WAFs replacing responses; API version skew between the k6 client and a newer/older cloud backend; structs hand-maintained in forks drifting from the real payload; connection instability truncating chunked bodies.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18).
Data as JSON: /api/errors/7e630998ae13b2c7.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cloudapi/provisioning/http_client.go:73
req.Header.Set("User-Agent", "k6cloud/"+p.version)
resp, err := doWithRetry(p.httpClient, req)
if err != nil {
return err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
if err := CheckResponse(resp); err != nil {
return err
}
if v != nil {
if err := json.NewDecoder(resp.Body).Decode(v); err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("decoding response: %w", err)
}
}
return nil
}
View on GitHub (pinned to 01ffac6f24)