{"record":{"id":"fd1f29f5f1ea0044","repo":"vxcontrol/pentagi","slug":"failed-to-decode-sploitus-response-w","errorCode":null,"errorMessage":"failed to decode Sploitus response: %w","messagePattern":"failed to decode Sploitus response: %w","errorType":"exception","errorClass":"Fatal","httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/searchers/sploitus.go","lineNumber":175,"sourceCode":"\tdefer resp.Body.Close()\n\n\t// Sploitus API returns 499 (and sometimes 422) when its rate limit is temporarily\n\t// exceeded — a transient condition that may clear on retry.\n\tif resp.StatusCode == 499 || resp.StatusCode == 422 {\n\t\treturn \"\", Retryable(fmt.Errorf(\"Sploitus API rate limit exceeded (HTTP %d), please try again later\", resp.StatusCode), 0)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terr := fmt.Errorf(\"Sploitus API returned HTTP %d\", resp.StatusCode)\n\t\tif resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {\n\t\t\treturn \"\", Retryable(err, 0)\n\t\t}\n\t\treturn \"\", Fatal(err)\n\t}\n\n\tvar apiResp sploitusResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {\n\t\treturn \"\", Fatal(fmt.Errorf(\"failed to decode Sploitus response: %w\", err))\n\t}\n\n\treturn formatSploitusResults(query, exploitType, limit, apiResp), nil\n}\n\n// IsAvailable returns true if the Sploitus tool is enabled and configured\nfunc (s *sploitus) IsAvailable() bool {\n\treturn s.enabled()\n}\n\nfunc (s *sploitus) enabled() bool {\n\treturn s.cfg != nil && s.cfg.SploitusEnabled\n}\n\n// sploitusRequest is the JSON body sent to the Sploitus search API\ntype sploitusRequest struct {\n\tQuery  string `json:\"query\"`\n\tType   string `json:\"type\"`","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/sploitus.go#L157-L193","documentation":"Decoding the Sploitus JSON response into sploitusResponse failed — the body was not valid JSON or did not match the expected shape; the searcher returns Fatal since retrying the identical request will likely produce the same body. Cloudflare HTML challenge pages or empty bodies commonly cause this.","triggerScenarios":"Handle() calls json.NewDecoder(resp.Body).Decode(&apiResp) after a 200 response whose body is HTML (Cloudflare interstitial), empty, truncated, or JSON with an incompatible schema.","commonSituations":"Cloudflare returns 200 with a challenge page, a proxy/SSL appliance rewrites the body, network truncation mid-body, or Sploitus changes its response schema (field type changes like string vs number).","solutions":["Capture and inspect the raw response body to see whether it is HTML, empty, or JSON","If it is a Cloudflare challenge, switch egress IP/proxy — headers alone may no longer suffice","Compare the body against sploitusResponse fields and update the struct to the current API schema","Read the body into memory first and log a snippet before decoding for easier diagnosis"],"exampleFix":"// before\nvar apiResp sploitusResponse\njson.NewDecoder(resp.Body).Decode(&apiResp)\n// after\nraw, _ := io.ReadAll(resp.Body)\nlog.Printf(\"sploitus raw body: %.200s\", raw)\nvar apiResp sploitusResponse\njson.Unmarshal(raw, &apiResp)","handlingStrategy":"try-catch","validationCode":"raw, err := io.ReadAll(resp.Body)\nif err != nil || len(bytes.TrimSpace(raw)) == 0 {\n    return fmt.Errorf(\"empty or unreadable Sploitus body\")\n}\nif !bytes.HasPrefix(bytes.TrimSpace(raw), []byte(\"{\")) {\n    return fmt.Errorf(\"non-JSON body (likely Cloudflare page): %.100s\", raw)\n}","typeGuard":null,"tryCatchPattern":"var apiResp sploitusResponse\nif err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {\n    return \"\", Fatal(fmt.Errorf(\"failed to decode Sploitus response: %w\", err))\n}","preventionTips":["Inspect raw body when decode fails before assuming schema drift","Detect Cloudflare challenge pages (HTML sniff) and switch egress IP","Update sploitusResponse fields whenever the upstream API changes","Log response content-type header as an early mismatch signal"],"tags":["json","deserialization","cloudflare","sploitus"],"backgroundTag":"json-decode-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}