vxcontrol/pentagi · warning · RetryableError

failed to read response body: %w

Error message

failed to read response body: %w

What it means

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.

Source

Thrown at backend/pkg/tools/searchers/perplexity.go:201

	if err != nil {
		return "", Retryable(fmt.Errorf("failed to send request: %w", err), 0)
	}
	defer resp.Body.Close()

	// Handling the response. handleErrorResponse keeps the per-status message; the
	// 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")

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the request — the error is typed Retryable for the orchestrator's fallback/retry path
  2. Increase the HTTP client timeout (p.timeout()) so body reads are not cut off
  3. Check intermediate proxies/VPN for connection-reset behavior on long responses
  4. If persistent, capture the wrapped cause (unexpected EOF vs context deadline) to target the network hop that drops the stream

Example fix

// before
client.Timeout = 5 * time.Second // body read truncated
// after
client.Timeout = 60 * time.Second
Defensive patterns

Strategy: retry

Validate before calling

// give body reads enough budget
client := &http.Client{Timeout: 60 * time.Second} // generous for large completions

Try / catch

_, err := engine.Handle(ctx, req)
if err != nil {
    if searchers.IsRetryable(err) && strings.Contains(err.Error(), "failed to read response body") {
        // transient stream truncation: retry with backoff
    }
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/0842e6660087b0b9. Report an issue: GitHub.