vxcontrol/pentagi · error · RetryableError

failed to send request: %w

Error message

failed to send request: %w

What it means

The POST to api.perplexity.ai failed at the transport level: `client.Do(req)` returned an error before an HTTP response existed. It is wrapped as Retryable (backend/pkg/tools/searchers/perplexity.go:184) because network failures are typically transient — the orchestrator may retry or fall back to another engine.

Source

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

	reqBody, err := json.Marshal(reqPayload)
	if err != nil {
		return "", Fatal(fmt.Errorf("failed to marshal request body: %w", err))
	}

	// Creating HTTP request
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, perplexityURL, bytes.NewBuffer(reqBody))
	if err != nil {
		return "", Fatal(fmt.Errorf("failed to create request: %w", err))
	}

	// Setting request headers
	req.Header.Set("Authorization", "Bearer "+p.apiKey())
	req.Header.Set("Content-Type", "application/json")

	// Sending the request
	resp, err := client.Do(req)
	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)
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Read the wrapped cause — it distinguishes DNS, timeout, TLS, and proxy failures
  2. Test connectivity from the container: `curl -v https://api.perplexity.ai/`
  3. Fix DNS/proxy/firewall so the container can reach api.perplexity.ai over TLS
  4. Increase the search timeout (PERPLEXITY timeout config) if deadlines are hit, or rely on the built-in Retryable fallback to another engine

Example fix

// before
curl https://api.perplexity.ai # fails: no egress
// after: allow egress or set proxy in .env
HTTPS_PROXY=http://proxy:3128
Defensive patterns

Strategy: retry

Validate before calling

// egress pre-check before relying on Perplexity
c := &http.Client{Timeout: 5 * time.Second}
if _, err := c.Head("https://api.perplexity.ai"); err != nil {
    // mark engine unavailable / rely on fallback engines
}

Try / catch

_, err := engine.Handle(ctx, req)
if err != nil {
    if searchers.IsRetryable(err) {
        // backoff and retry, or fall back to another engine in fallbackStrategy
    }
}

Prevention

When it happens

Trigger: `client.Do` errors on DNS resolution failure, connection refused/timeout, TLS handshake failure, proxy unreachability, or the request context being canceled (deadline/shutdown) during the call.

Common situations: Container without internet access or broken DNS; api.perplexity.ai blocked by firewall/corporate proxy; misconfigured proxy for the HTTP client; client.Timeout too small for slow links; ctx deadline exceeded during heavy flows.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


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