charmbracelet/crush · error

search failed with status code: %d

Error message

search failed with status code: %d

What it means

DuckDuckGo returned a non-200, non-202 status (e.g. 403 Forbidden, 429 Too Many Requests, 5xx). The tool treats 202 as rate-limit anomaly challenge; every other non-OK status is rejected with this error since the body won't be a parsable result page. It reflects server-side rejection, not a client bug.

Source

Thrown at internal/agent/tools/search.go:97

		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	setRandomizedHeaders(req)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute search: %w", err)
	}
	defer resp.Body.Close()

	// A 202 from DuckDuckGo is the anomaly-challenge interstitial, not a
	// result page; report throttling rather than parsing it into an
	// empty result set.
	if resp.StatusCode == http.StatusAccepted {
		return nil, errSearchRateLimited
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("search failed with status code: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	content := string(body)
	for _, marker := range ddgAnomalyMarkers {
		if strings.Contains(content, marker) {
			return nil, errSearchRateLimited
		}
	}

	return parseLiteSearchResults(content, maxResults)
}

func setRandomizedHeaders(req *http.Request) {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the logged status code: 429/403 means back off and slow the request rate
  2. Add jitter/backoff and cache results to reduce request volume
  3. Consider routing through a different network/egress if the IP is blocked
  4. Retry later on 5xx — the failure is server-side

Example fix

// before
for _, q := range queries { search(q) } // tight loop, triggers 403/429
// after
for i, q := range queries {
    if i > 0 { time.Sleep(2*time.Second + jitter()) }
    search(q)
}
Defensive patterns

Strategy: retry

Validate before calling

// after the call, guard before parsing
if resp.StatusCode == http.StatusAccepted { /* rate-limited */ }
if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("search unavailable (status %d), backing off", resp.StatusCode)
}

Try / catch

if errors.Is(err, errSearchRateLimited) || strings.Contains(err.Error(), "status code: 4") {
    select {
    case <-time.After(backoffWithJitter()):
        return searchDuckDuckGo(ctx, query) // bounded retry
    case <-ctx.Done():
        return ctx.Err()
    }
}

Prevention

When it happens

Trigger: searchDuckDuckGo receives resp.StatusCode not in {200, 202} — typically 403 (bot detection/block), 429 (rate limited beyond challenge), or 500-503 (server-side outage).

Common situations: Hammering the endpoint from many requests triggers bot-blocking (403); regional blocks; DuckDuckGo serving maintenance errors; very heavy automated usage from one IP.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/b5d2d51c7f958676. Report an issue: GitHub.