vxcontrol/pentagi · error · FatalError

duckduckgo search failed: %w

Error message

duckduckgo search failed: %w

What it means

The DuckDuckGo searcher wraps any failure from its internal search() into a Fatal-classified error (`searchers.Fatal`). Per the searchers contract, Fatal means the orchestrator should NOT retry this engine and should move to the next engine in the fallbackStrategy chain. DuckDuckGo already retries transient failures internally, so anything surfacing here is considered terminal for this engine.

Source

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

		observation.Event(
			langfuse.WithEventName("search engine error"),
			langfuse.WithEventInput(req.Query),
			langfuse.WithEventStatus(err.Error()),
			langfuse.WithEventLevel(langfuse.ObservationLevelWarning),
			langfuse.WithEventMetadata(langfuse.Metadata{
				"engine":      "duckduckgo",
				"query":       req.Query,
				"max_results": numResults,
				"region":      d.region(),
				"error":       err.Error(),
			}),
		)

		obs.LogErrorOrCancel(logger, err, "failed to search in DuckDuckGo")
		// DuckDuckGo already retries transient failures internally (see search);
		// by the time an error surfaces here, moving to the next engine is the
		// right call rather than burning another round-trip on the same one.
		return "", Fatal(fmt.Errorf("duckduckgo search failed: %w", err))
	}

	return result, nil
}

// search performs a web search using DuckDuckGo
func (d *duckduckgo) search(ctx context.Context, query string, maxResults int) (string, error) {
	// Build form data for POST request
	formData := d.buildFormData(query)

	// Create HTTP client with proper configuration
	client, err := system.GetHTTPClient(d.cfg)
	if err != nil {
		return "", fmt.Errorf("failed to create http client: %w", err)
	}

	client.Timeout = duckduckgoTimeout

View on GitHub (pinned to ea665308ba)

Solutions

  1. Inspect the wrapped cause: if it's a network/status error, check egress connectivity and proxy settings (HTTP_PROXY/HTTPS_PROXY, cfg proxy config) for the container.
  2. If it's a parse error, DuckDuckGo likely changed its HTML; update the selectors in parseHTMLStructured/parseHTMLRegex.
  3. Verify other engines in the web_search fallback chain are configured so orchestration continues despite this Fatal error.
  4. Reduce request volume or rotate egress IPs if you're seeing repeated 403s (rate-limit/bot detection).
Defensive patterns

Strategy: fallback

Validate before calling

// before calling web_search, check the engine is reachable
resp, err := http.Head("https://duckduckgo.com")
if err != nil || resp.StatusCode >= 500 {
    // expect the duckduckgo searcher to fail; ensure other engines configured
}

Type guard

var fatal searchers.FatalError
if errors.As(err, &fatal) {
    // do not retry this engine; orchestrator moves to next engine
}

Try / catch

result, err := webSearch.Handle(ctx, req)
if err != nil {
    if searchers.IsFatal(err) {
        log.Warn("duckduckgo failed terminally; falling back configured engines")
    }
    return err // orchestrator already applied fallback chain
}

Prevention

When it happens

Trigger: Any error returned by d.search() after internal retries: HTTP client construction failure, request-build failure, transport errors after duckduckgoMaxRetries attempts, non-200 status after retries, body-read error, or HTML-parse error.

Common situations: DuckDuckGo rate-limiting or blocking the host (anomaly 403/202 responses); corporate egress proxy blocking duckduckgo.com; TLS interception breaking requests; DuckDuckGo changing HTML layout so parsing yields no results path-related errors; no internet/DNS failure in the container.

Related errors


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