crowdsecurity/crowdsec · error

failed to decode response: %w

Error message

failed to decode response: %w

What it means

When the PAPI /permissions endpoint answers with a non-200 status, GetPermissions tries to decode the body as PapiPermCheckError{error}. This error means even that error payload could not be JSON-decoded — the response body wasn't the expected JSON error object (e.g. an HTML page, empty body, or different schema). The real HTTP failure status is then obscured, so only the status code path is known to be bad.

Source

Thrown at pkg/apiserver/papi.go:184

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, papiCheckURL, http.NoBody)
	if err != nil {
		return PapiPermCheckSuccess{}, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return PapiPermCheckSuccess{}, fmt.Errorf("failed to get response: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		errResp := PapiPermCheckError{}

		err = json.NewDecoder(resp.Body).Decode(&errResp)
		if err != nil {
			return PapiPermCheckSuccess{}, fmt.Errorf("failed to decode response: %w", err)
		}

		return PapiPermCheckSuccess{}, fmt.Errorf("unable to query PAPI : %s (%d)", errResp.Error, resp.StatusCode)
	}

	respBody := PapiPermCheckSuccess{}

	err = json.NewDecoder(resp.Body).Decode(&respBody)
	if err != nil {
		return PapiPermCheckSuccess{}, fmt.Errorf("failed to decode response: %w", err)
	}

	return respBody, nil
}

func reverse(s []longpollclient.Event) []longpollclient.Event {
	a := make([]longpollclient.Event, len(s))
	copy(a, s)

View on GitHub (pinned to 909b515798)

Solutions

  1. Log/inspect the raw response body and status code at the moment of failure (curl the same URL with the machine credentials).
  2. If it's a CDN/502/503, treat it as a transient CAPI outage and retry later; PAPI permission checks re-run periodically.
  3. Remove any misbehaving proxy between the host and api.crowdsec.net.
  4. Ensure papi_url points to the real PAPI endpoint; upgrade crowdsec if the API's error schema changed.
Defensive patterns

Strategy: retry

Try / catch

perms, err := papi.GetPermissions(ctx)
if err != nil {
    if strings.Contains(err.Error(), "failed to decode response") || strings.Contains(err.Error(), "unable to query PAPI") {
        // non-200 from PAPI with unexpected body: transient upstream issue — retry with backoff
        return retryWithBackoff(ctx)
    }
}

Prevention

When it happens

Trigger: resp.StatusCode != http.StatusOK and json.NewDecoder(resp.Body).Decode(&errResp) fails inside GetPermissions — the endpoint returned e.g. a 502/503 HTML gateway error page, an empty body, or rate-limit text instead of {"error": ...}.

Common situations: CAPI outage or maintenance returning gateway HTML; a proxy/CDN interposing an error page; severe rate limiting returning plain-text responses; pointing papi_url at a non-PAPI host that answers 404 HTML.

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 crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/1623953ae2dddb10. Report an issue: GitHub.