{"record":{"id":"0842e6660087b0b9","repo":"vxcontrol/pentagi","slug":"failed-to-read-response-body-w-0842e6","errorCode":null,"errorMessage":"failed to read response body: %w","messagePattern":"failed to read response body: %w","errorType":"exception","errorClass":"RetryableError","httpStatus":null,"severity":"warning","filePath":"backend/pkg/tools/searchers/perplexity.go","lineNumber":201,"sourceCode":"\tif err != nil {\n\t\treturn \"\", Retryable(fmt.Errorf(\"failed to send request: %w\", err), 0)\n\t}\n\tdefer resp.Body.Close()\n\n\t// Handling the response. handleErrorResponse keeps the per-status message; the\n\t// retryable/fatal classification is decided here from the status code.\n\tif resp.StatusCode != http.StatusOK {\n\t\tbaseErr := p.handleErrorResponse(resp.StatusCode)\n\t\tif resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {\n\t\t\treturn \"\", Retryable(baseErr, 0)\n\t\t}\n\t\treturn \"\", Fatal(baseErr)\n\t}\n\n\t// Reading the response body\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", Retryable(fmt.Errorf(\"failed to read response body: %w\", err), 0)\n\t}\n\n\t// Deserializing the response\n\tvar response CompletionResponse\n\tif err := json.Unmarshal(body, &response); err != nil {\n\t\treturn \"\", Fatal(fmt.Errorf(\"failed to unmarshal response: %w\", err))\n\t}\n\n\t// Forming the result\n\tresult := p.formatResponse(ctx, &response, query)\n\treturn result, nil\n}\n\n// handleErrorResponse handles erroneous HTTP statuses\nfunc (p *perplexity) handleErrorResponse(statusCode int) error {\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\treturn errors.New(\"request is invalid\")","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/perplexity.go#L183-L219","documentation":"The API responded with HTTP 200 but `io.ReadAll(resp.Body)` failed while reading the stream — connection reset mid-body, chunk encoding error, or context canceled during read. Wrapped as Retryable at backend/pkg/tools/searchers/perplexity.go:201 since re-issuing the request usually succeeds.","triggerScenarios":"`search` passes the status check (200 OK), then ReadAll aborts: server closed the connection before EOF, truncated chunked transfer, transient proxy error mid-stream, or ctx deadline hit while draining the body.","commonSituations":"Flaky load balancer/proxy between PentAGI and Perplexity dropping long responses; client timeout slightly too small so the deadline fires during body read; TLS interception appliance resetting large responses.","solutions":["Retry the request — the error is typed Retryable for the orchestrator's fallback/retry path","Increase the HTTP client timeout (p.timeout()) so body reads are not cut off","Check intermediate proxies/VPN for connection-reset behavior on long responses","If persistent, capture the wrapped cause (unexpected EOF vs context deadline) to target the network hop that drops the stream"],"exampleFix":"// before\nclient.Timeout = 5 * time.Second // body read truncated\n// after\nclient.Timeout = 60 * time.Second","handlingStrategy":"retry","validationCode":"// give body reads enough budget\nclient := &http.Client{Timeout: 60 * time.Second} // generous for large completions","typeGuard":null,"tryCatchPattern":"_, err := engine.Handle(ctx, req)\nif err != nil {\n    if searchers.IsRetryable(err) && strings.Contains(err.Error(), \"failed to read response body\") {\n        // transient stream truncation: retry with backoff\n    }\n}","preventionTips":["Increase client timeout so 200-OK body draining is not cut off by the deadline","Check intermediate proxies/TLS-inspection appliances for dropping large responses","Watch for unexpected EOF patterns in logs to identify the failing network hop","Prefer engines with local fallbacks so one flaky path does not break search"],"tags":["network","http","response-body"],"backgroundTag":"response-body-read-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}