vxcontrol/pentagi · error

failed to execute search after %d attempts: %w

Error message

failed to execute search after %d attempts: %w

What it means

The POST to DuckDuckGo failed on every one of duckduckgoMaxRetries attempts; on the final attempt the last transport error is wrapped and returned. Transient failures between attempts sleep 1s (or return ctx.Err() if the context is cancelled). Upstream, Handle classifies this as Fatal so the orchestrator falls back to the next engine.

Source

Thrown at backend/pkg/tools/searchers/duckduckgo.go:162

	// Execute request with retry logic
	var response *searchResponse
	for attempt := 0; attempt < duckduckgoMaxRetries; attempt++ {
		req, err := http.NewRequestWithContext(ctx, "POST", duckduckgoSearchURL, strings.NewReader(formData))
		if err != nil {
			return "", fmt.Errorf("failed to create search request: %w", err)
		}

		// Add necessary headers for POST request
		req.Header.Set("User-Agent", duckduckgoUserAgent)
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
		req.Header.Set("Accept-Language", "en-US,en;q=0.9")

		resp, err := client.Do(req)
		if err != nil {
			if attempt == duckduckgoMaxRetries-1 {
				return "", fmt.Errorf("failed to execute search after %d attempts: %w", duckduckgoMaxRetries, err)
			}
			select {
			case <-ctx.Done():
				return "", ctx.Err()
			case <-time.After(time.Second):
			}
			continue
		}

		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			if attempt == duckduckgoMaxRetries-1 {
				return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
			}
			select {
			case <-ctx.Done():
				return "", ctx.Err()
			case <-time.After(time.Second):

View on GitHub (pinned to ea665308ba)

Solutions

  1. Read the wrapped cause (e.g. 'dial tcp ... i/o timeout', 'no such host', 'connection refused') and fix the underlying network/DNS/proxy issue.
  2. Confirm egress from the container: `docker exec <c> wget -qO- https://duckduckgo.com` to isolate host vs container networking.
  3. Increase duckduckgoTimeout or retry backoff if failures are borderline timeouts on slow networks.
  4. Ensure a fallback engine (Google, Tavily, etc.) is configured so web_search still returns results.
Defensive patterns

Strategy: retry

Validate before calling

// check egress before issuing searches
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := checkConnectivity(ctx, "https://duckduckgo.com"); err != nil {
    // engine unavailable; skip to fallback engine
}

Type guard

var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
    // transient timeout; safe to retry with backoff
}

Try / catch

result, err := d.search(ctx, query, numResults)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
        return "", err // don't wrap cancellation
    }
    return "", Fatal(fmt.Errorf("duckduckgo search failed: %w", err))
}

Prevention

When it happens

Trigger: client.Do returns an error on all retries: DNS resolution failure, TCP connect refused/timeout, TLS handshake failure, context deadline exceeded while the request is in flight on the last attempt, or proxy unreachable.

Common situations: Container has no internet or broken DNS; corporate firewall blocks duckduckgo.com; DuckDuckGo rate-limits/blocks your IP leading to connection resets; http client timeout (duckduckgoTimeout) too short on a slow link; proxy misconfigured.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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