vxcontrol/pentagi · error · FatalError
failed to decode response body: %v
Error message
failed to decode response body: %v
What it means
On HTTP 200, the response body is JSON-decoded into firecrawlSearchResult; a decode failure is Fatal because the engine answered but with an unreadable body — retrying immediately would likely reproduce it.
Source
Thrown at backend/pkg/tools/searchers/firecrawl.go:179
req = req.WithContext(ctx)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+f.apiKey())
resp, err := client.Do(req)
if err != nil {
return "", Retryable(fmt.Errorf("failed to do request: %v", err), 0)
}
defer resp.Body.Close()
return f.parseHTTPResponse(ctx, query, resp)
}
func (f *firecrawl) parseHTTPResponse(ctx context.Context, query string, resp *http.Response) (string, error) {
switch resp.StatusCode {
case http.StatusOK:
var respBody firecrawlSearchResult
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
return "", Fatal(fmt.Errorf("failed to decode response body: %v", err))
}
if !respBody.Success {
if respBody.Error != "" {
return "", Fatal(fmt.Errorf("request failed: %s", respBody.Error))
}
return "", Fatal(fmt.Errorf("request failed"))
}
return f.buildFirecrawlResult(ctx, query, &respBody), nil
case http.StatusBadRequest:
return "", Fatal(fmt.Errorf("request is invalid"))
case http.StatusUnauthorized:
return "", Fatal(fmt.Errorf("API key is wrong"))
case http.StatusPaymentRequired:
return "", Fatal(fmt.Errorf("insufficient credits to perform this request"))
case http.StatusForbidden:
return "", Fatal(fmt.Errorf("the endpoint requested is hidden for administrators only"))
case http.StatusNotFound:
return "", Fatal(fmt.Errorf("the specified endpoint could not be found"))View on GitHub (pinned to ea665308ba)
Solutions
- Log the raw body (first N bytes) at the failure point to see what was returned.
- Verify the request actually reached Firecrawl, not an auth wall of a proxy.
- Pin/upgrade to the Firecrawl API version matching the struct (v2).
- Retry once with a plain http.Client (no proxy) to rule out transport rewriting.
Example fix
// before timeout int `json:"timeout"` // after (if API now returns string) timeout json.Number `json:"timeout"`
Defensive patterns
Strategy: validation
Validate before calling
body, _ := io.ReadAll(resp.Body)
if !json.Valid(body) {
return fmt.Errorf("non-JSON response: %.200s", body)
} Prevention
- Log raw bodies on decode failure for diagnosis.
- Pin the Firecrawl API version matching the response structs.
- Bypass intercepting proxies when testing engine integration.
- Add an integration test decoding a recorded real response.
When it happens
Trigger: Engine returns 200 with non-JSON body (HTML error/login page from a proxy), truncated body, or an API schema change breaking field types (string vs object).
Common situations: Captive portal/proxy intercepting traffic, Firecrawl API version change altering the /v2/search response shape, gzip/charset handling issue via custom transport.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to marshal request body: %v
- failed to decode response body: %v
- knowledge: marshal cmetadata: %w
- failed to unmarshal primary agent msg chain %d: %w
- failed to marshal provider config: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/1554fa2680a05ab3.
Report an issue: GitHub.