{"record":{"id":"d44bd443809af6f6","repo":"vxcontrol/pentagi","slug":"failed-to-decode-response-body-w","errorCode":null,"errorMessage":"failed to decode response body: %w","messagePattern":"failed to decode response body: %w","errorType":"exception","errorClass":"Fatal","httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/searchers/searxng.go","lineNumber":143,"sourceCode":"\t\treturn \"\", Retryable(fmt.Errorf(\"failed to do request: %w\", err), 0)\n\t}\n\tdefer resp.Body.Close()\n\n\treturn s.parseHTTPResponse(resp, query)\n}\n\nfunc (s *searxng) parseHTTPResponse(resp *http.Response, query string) (string, error) {\n\tif resp.StatusCode != http.StatusOK {\n\t\terr := fmt.Errorf(\"unexpected status code: %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 searxngResponse SearxngResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {\n\t\treturn \"\", Fatal(fmt.Errorf(\"failed to decode response body: %w\", err))\n\t}\n\n\treturn s.formatResults(searxngResponse.Results, query), nil\n}\n\nfunc (s *searxng) formatResults(results []SearxngResult, query string) string {\n\tif len(results) == 0 {\n\t\treturn fmt.Sprintf(\"# No Results Found\\n\\nNo results were found for query: %s\", query)\n\t}\n\n\tvar builder strings.Builder\n\tbuilder.WriteString(fmt.Sprintf(\"# Searxng Search Results\\n\\n## Query: %s\\n\\n\", query))\n\tbuilder.WriteString(\"Results from Searxng meta search engine (aggregated from multiple search engines):\\n\\n\")\n\n\tfor i, result := range results {\n\t\tbuilder.WriteString(fmt.Sprintf(\"### %d. %s\\n\\n\", i+1, result.Title))\n\n\t\tif result.URL != \"\" {","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/searxng.go#L125-L161","documentation":"The SearxNG response had HTTP 200 but its body could not be JSON-decoded into SearxngResponse. Despite the 200 status, the body was not the expected JSON envelope ({query, results, info}) — usually HTML or empty content. Classified Fatal because a 200 with a non-JSON body will not improve on retry.","triggerScenarios":"SearxNG returning a 200 HTML page (e.g. some instances/proxies render an HTML search page even when format=json is requested, or an error interstitial); empty body from a proxy; response body truncated mid-JSON by a proxy buffer limit.","commonSituations":"Reverse proxy or WAF in front of SearxNG rewriting responses; SearxNG plugin producing HTML error pages with 200; an older SearxNG version incompatible with the requested parameters that still returns 200 HTML; gzip/encoding issues where a hand-rolled client misses Accept-Encoding handling.","solutions":["Log the first bytes of resp.Body before decoding to see whether the body is HTML or empty","Enable JSON format on the SearxNG instance (search.formats: [html, json]) — some versions serve HTML on 200 when json is disallowed","Check any reverse proxy (nginx/traefik) in front of SearxNG for response rewriting, buffering limits, or content-type interception","Update SearxNG to a current version; old versions may respond unexpectedly to limit/time_range parameters","Pin the Content-Type check: verify the response Content-Type is application/json before decoding and fail with a clearer error otherwise"],"exampleFix":"// before\nvar searxngResponse SearxngResponse\nif err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {\n    return \"\", Fatal(fmt.Errorf(\"failed to decode response body: %w\", err))\n}\n// after\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return \"\", Fatal(fmt.Errorf(\"unexpected content type %q from searxng\", ct))\n}\nvar searxngResponse SearxngResponse\nif err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {\n    return \"\", Fatal(fmt.Errorf(\"failed to decode response body: %w\", err))\n}","handlingStrategy":"validation","validationCode":"// validate Content-Type and preview body before decoding\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"searxng returned %q instead of JSON\", ct)\n}\nvar r SearxngResponse\nif err := json.NewDecoder(resp.Body).Decode(&r); err != nil {\n    return fmt.Errorf(\"non-JSON 200 body from searxng: %w\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check Content-Type is application/json before decoding; log a body preview on failure","Ensure JSON format is enabled on the instance so 200s are always JSON","Review reverse-proxy/WAF configs for response rewriting or truncation of SearxNG responses","Keep SearxNG updated; old versions can return 200 HTML for unsupported parameters","Remember the error is Fatal — the orchestrator fallback handles engine failover automatically"],"tags":["json","http-response","parsing","searxng"],"backgroundTag":"invalid-json-response","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}