vxcontrol/pentagi · error · Fatal
failed to decode response body: %w
Error message
failed to decode response body: %w
What it means
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.
Source
Thrown at backend/pkg/tools/searchers/searxng.go:143
return "", Retryable(fmt.Errorf("failed to do request: %w", err), 0)
}
defer resp.Body.Close()
return s.parseHTTPResponse(resp, query)
}
func (s *searxng) parseHTTPResponse(resp *http.Response, query string) (string, error) {
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("unexpected status code: %d", resp.StatusCode)
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
return "", Retryable(err, 0)
}
return "", Fatal(err)
}
var searxngResponse SearxngResponse
if err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {
return "", Fatal(fmt.Errorf("failed to decode response body: %w", err))
}
return s.formatResults(searxngResponse.Results, query), nil
}
func (s *searxng) formatResults(results []SearxngResult, query string) string {
if len(results) == 0 {
return fmt.Sprintf("# No Results Found\n\nNo results were found for query: %s", query)
}
var builder strings.Builder
builder.WriteString(fmt.Sprintf("# Searxng Search Results\n\n## Query: %s\n\n", query))
builder.WriteString("Results from Searxng meta search engine (aggregated from multiple search engines):\n\n")
for i, result := range results {
builder.WriteString(fmt.Sprintf("### %d. %s\n\n", i+1, result.Title))
if result.URL != "" {View on GitHub (pinned to ea665308ba)
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
Example fix
// before
var searxngResponse SearxngResponse
if err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {
return "", Fatal(fmt.Errorf("failed to decode response body: %w", err))
}
// after
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return "", Fatal(fmt.Errorf("unexpected content type %q from searxng", ct))
}
var searxngResponse SearxngResponse
if err := json.NewDecoder(resp.Body).Decode(&searxngResponse); err != nil {
return "", Fatal(fmt.Errorf("failed to decode response body: %w", err))
} Defensive patterns
Strategy: validation
Validate before calling
// validate Content-Type and preview body before decoding
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return fmt.Errorf("searxng returned %q instead of JSON", ct)
}
var r SearxngResponse
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
return fmt.Errorf("non-JSON 200 body from searxng: %w", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to unmarshal response: %w
- failed to parse get_flow_status args: %w
- failed to decode response body: %v
- knowledge: marshal cmetadata: %w
- failed to unmarshal primary agent msg chain %d: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/d44bd443809af6f6.
Report an issue: GitHub.