{"record":{"id":"1554fa2680a05ab3","repo":"vxcontrol/pentagi","slug":"failed-to-decode-response-body-v","errorCode":null,"errorMessage":"failed to decode response body: %v","messagePattern":"failed to decode response body: (.+?)","errorType":"exception","errorClass":"FatalError","httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/searchers/firecrawl.go","lineNumber":179,"sourceCode":"\treq = req.WithContext(ctx)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+f.apiKey())\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", Retryable(fmt.Errorf(\"failed to do request: %v\", err), 0)\n\t}\n\tdefer resp.Body.Close()\n\n\treturn f.parseHTTPResponse(ctx, query, resp)\n}\n\nfunc (f *firecrawl) parseHTTPResponse(ctx context.Context, query string, resp *http.Response) (string, error) {\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar respBody firecrawlSearchResult\n\t\tif err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {\n\t\t\treturn \"\", Fatal(fmt.Errorf(\"failed to decode response body: %v\", err))\n\t\t}\n\t\tif !respBody.Success {\n\t\t\tif respBody.Error != \"\" {\n\t\t\t\treturn \"\", Fatal(fmt.Errorf(\"request failed: %s\", respBody.Error))\n\t\t\t}\n\t\t\treturn \"\", Fatal(fmt.Errorf(\"request failed\"))\n\t\t}\n\t\treturn f.buildFirecrawlResult(ctx, query, &respBody), nil\n\tcase http.StatusBadRequest:\n\t\treturn \"\", Fatal(fmt.Errorf(\"request is invalid\"))\n\tcase http.StatusUnauthorized:\n\t\treturn \"\", Fatal(fmt.Errorf(\"API key is wrong\"))\n\tcase http.StatusPaymentRequired:\n\t\treturn \"\", Fatal(fmt.Errorf(\"insufficient credits to perform this request\"))\n\tcase http.StatusForbidden:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the endpoint requested is hidden for administrators only\"))\n\tcase http.StatusNotFound:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the specified endpoint could not be found\"))","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/firecrawl.go#L161-L197","documentation":"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.","triggerScenarios":"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).","commonSituations":"Captive portal/proxy intercepting traffic, Firecrawl API version change altering the /v2/search response shape, gzip/charset handling issue via custom transport.","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."],"exampleFix":"// before\ntimeout int `json:\"timeout\"`\n// after (if API now returns string)\ntimeout json.Number `json:\"timeout\"`","handlingStrategy":"validation","validationCode":"body, _ := io.ReadAll(resp.Body)\nif !json.Valid(body) {\n  return fmt.Errorf(\"non-JSON response: %.200s\", body)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["json","response-parsing","firecrawl","searchers"],"backgroundTag":"json-decode-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}