vxcontrol/pentagi · error · FatalError
failed to unmarshal response: %w
Error message
failed to unmarshal response: %w
What it means
This error is thrown by the Perplexity searcher when the HTTP response body returned from https://api.perplexity.ai/chat/completions cannot be deserialized into the CompletionResponse struct via json.Unmarshal. It is classified as Fatal (not retried) because a body that fails JSON parsing on a 200 response is unlikely to change on retry. Commonly it means the server returned non-JSON content (HTML error page, proxy login page, empty body) despite a 200 status.
Source
Thrown at backend/pkg/tools/searchers/perplexity.go:207
// retryable/fatal classification is decided here from the status code.
if resp.StatusCode != http.StatusOK {
baseErr := p.handleErrorResponse(resp.StatusCode)
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
return "", Retryable(baseErr, 0)
}
return "", Fatal(baseErr)
}
// Reading the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", Retryable(fmt.Errorf("failed to read response body: %w", err), 0)
}
// Deserializing the response
var response CompletionResponse
if err := json.Unmarshal(body, &response); err != nil {
return "", Fatal(fmt.Errorf("failed to unmarshal response: %w", err))
}
// Forming the result
result := p.formatResponse(ctx, &response, query)
return result, nil
}
// handleErrorResponse handles erroneous HTTP statuses
func (p *perplexity) handleErrorResponse(statusCode int) error {
switch statusCode {
case http.StatusBadRequest:
return errors.New("request is invalid")
case http.StatusUnauthorized:
return errors.New("API key is wrong")
case http.StatusForbidden:
return errors.New("the endpoint requested is hidden for administrators only")
case http.StatusNotFound:
return errors.New("the specified endpoint could not be found")View on GitHub (pinned to ea665308ba)
Solutions
- Log the raw response body (string(body)) at the failure point to see what was actually returned
- Verify no corporate proxy/MITM is rewriting api.perplexity.ai responses (check HTTPS_PROXY env, TLS interception certs)
- Check Perplexity API changelog for response schema changes to model/choices/citations fields and update the CompletionResponse struct
- Confirm the API key is valid — some invalid-credential paths return HTML instead of JSON
- Retry the query; if transient truncation, the search orchestrator's fallback chain will try another engine
Example fix
// before
var response CompletionResponse
if err := json.Unmarshal(body, &response); err != nil {
return "", Fatal(fmt.Errorf("failed to unmarshal response: %w", err))
}
// after
var response CompletionResponse
if err := json.Unmarshal(body, &response); err != nil {
return "", Fatal(fmt.Errorf("failed to unmarshal response (status %d, body %q): %w", resp.StatusCode, string(body[:min(len(body), 512)]), err))
} Defensive patterns
Strategy: validation
Validate before calling
// check Content-Type and empty body before unmarshal
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
return fmt.Errorf("perplexity returned non-JSON content type %q", ct)
}
if len(body) == 0 {
return fmt.Errorf("perplexity returned empty body")
}
var response CompletionResponse
if err := json.Unmarshal(body, &response); err != nil {
return fmt.Errorf("failed to unmarshal response (body %q): %w", string(body[:min(len(body),512)]), err)
} Prevention
- Always log the raw response body on unmarshal failure to diagnose proxy injection
- Verify API key validity before relying on the engine (IsAvailable only checks non-empty)
- Test through corporate proxies; check for TLS interception affecting api.perplexity.ai
- Pin and review Perplexity API changelog for response schema changes
When it happens
Trigger: A POST to the Perplexity chat/completions endpoint returns HTTP 200 but the body is not valid JSON matching the expected schema — e.g. an HTML page injected by a corporate MITM proxy, a truncated response, or Perplexity returning a JSON error object that fails strict unmarshal into the struct fields.
Common situations: Corporate proxy/firewall or captive portal intercepting api.perplexity.ai traffic; Perplexity API deprecating or changing response fields so types no longer match; network middleboxes truncating large sonar-pro responses; using a wrong server URL override that points to an HTML page.
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.
Related errors
- failed to decode response body: %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/7bd0b4f6599752b1.
Report an issue: GitHub.