{"record":{"id":"1623953ae2dddb10","repo":"crowdsecurity/crowdsec","slug":"failed-to-decode-response-w","errorCode":null,"errorMessage":"failed to decode response: %w","messagePattern":"failed to decode response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/apiserver/papi.go","lineNumber":184,"sourceCode":"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, papiCheckURL, http.NoBody)\n\tif err != nil {\n\t\treturn PapiPermCheckSuccess{}, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn PapiPermCheckSuccess{}, fmt.Errorf(\"failed to get response: %w\", err)\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terrResp := PapiPermCheckError{}\n\n\t\terr = json.NewDecoder(resp.Body).Decode(&errResp)\n\t\tif err != nil {\n\t\t\treturn PapiPermCheckSuccess{}, fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t}\n\n\t\treturn PapiPermCheckSuccess{}, fmt.Errorf(\"unable to query PAPI : %s (%d)\", errResp.Error, resp.StatusCode)\n\t}\n\n\trespBody := PapiPermCheckSuccess{}\n\n\terr = json.NewDecoder(resp.Body).Decode(&respBody)\n\tif err != nil {\n\t\treturn PapiPermCheckSuccess{}, fmt.Errorf(\"failed to decode response: %w\", err)\n\t}\n\n\treturn respBody, nil\n}\n\nfunc reverse(s []longpollclient.Event) []longpollclient.Event {\n\ta := make([]longpollclient.Event, len(s))\n\tcopy(a, s)","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/papi.go#L166-L202","documentation":"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.","triggerScenarios":"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\": ...}.","commonSituations":"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.","solutions":["Log/inspect the raw response body and status code at the moment of failure (curl the same URL with the machine credentials).","If it's a CDN/502/503, treat it as a transient CAPI outage and retry later; PAPI permission checks re-run periodically.","Remove any misbehaving proxy between the host and api.crowdsec.net.","Ensure papi_url points to the real PAPI endpoint; upgrade crowdsec if the API's error schema changed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"perms, err := papi.GetPermissions(ctx)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to decode response\") || strings.Contains(err.Error(), \"unable to query PAPI\") {\n        // non-200 from PAPI with unexpected body: transient upstream issue — retry with backoff\n        return retryWithBackoff(ctx)\n    }\n}","preventionTips":["Ensure no intercepting proxy/CDN sits between the host and api.crowdsec.net.","Handle expected error statuses (4xx/5xx) gracefully instead of assuming JSON error bodies.","Subscribe to CrowdSec status updates to correlate failures with CAPI incidents.","Upgrade crowdsec when the PAPI error schema changes so both sides speak the same shape."],"tags":["papi","http","json","api"],"backgroundTag":"invalid-json-response","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}