{"record":{"id":"d37e4a689e43dc44","repo":"charmbracelet/crush","slug":"failed-to-create-request-w-d37e4a","errorCode":null,"errorMessage":"failed to create request: %w","messagePattern":"failed to create request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/search.go","lineNumber":79,"sourceCode":"\t\"anomaly-modal\",\n\t\"/anomaly.js\",\n\t\"Unfortunately, bots use DuckDuckGo too\",\n}\n\n// ddgLiteEndpoint is a package var so tests can point the search at a\n// local httptest server.\nvar ddgLiteEndpoint = \"https://lite.duckduckgo.com/lite/?q=\"\n\nfunc searchDuckDuckGo(ctx context.Context, client *http.Client, query string, maxResults int) ([]SearchResult, error) {\n\tif maxResults <= 0 {\n\t\tmaxResults = 10\n\t}\n\n\tsearchURL := ddgLiteEndpoint + url.QueryEscape(query)\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\tsetRandomizedHeaders(req)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute search: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// A 202 from DuckDuckGo is the anomaly-challenge interstitial, not a\n\t// result page; report throttling rather than parsing it into an\n\t// empty result set.\n\tif resp.StatusCode == http.StatusAccepted {\n\t\treturn nil, errSearchRateLimited\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"search failed with status code: %d\", resp.StatusCode)","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/search.go#L61-L97","documentation":"searchDuckDuckGo builds an outbound http.NewRequestWithContext GET to the DuckDuckGo lite endpoint; if http.NewRequestWithContext itself errors (it only fails on malformed URLs or an invalid context), this wrapped error is returned before any network I/O. In practice this almost always means the escaped query produced an unparsable URL.","triggerScenarios":"http.NewRequestWithContext(ctx, \"GET\", ddgLiteEndpoint+url.QueryEscape(query), nil) returns an error — typically a control character or malformed URL after escaping, or an already-canceled ctx passed at construction.","commonSituations":"Query strings containing raw newlines/control bytes forwarded from model output; endpoint constant corrupted by config override; passing a canceled context.","solutions":["Sanitize the query: strip control characters/newlines before calling the tool","Inspect the wrapped error's URL parse message for the offending character","Verify the search endpoint configuration hasn't been overridden with an invalid URL","Check ctx cancellation if constructing with a pre-canceled context"],"exampleFix":"// before\nquery = strings.TrimSpace(rawQuery) // may still contain \\n or control bytes\n// after\nquery = strings.Map(func(r rune) rune {\n    if r < 0x20 { return -1 }\n    return r\n}, rawQuery)","handlingStrategy":"validation","validationCode":"func sanitizeQuery(q string) string {\n    return strings.Map(func(r rune) rune {\n        if r < 0x20 || r == 0x7f { return -1 }\n        return r\n    }, strings.TrimSpace(q))\n}\nquery = sanitizeQuery(query)","typeGuard":null,"tryCatchPattern":"var urlErr *url.Error\nif err := searchDuckDuckGo(ctx, q); err != nil && errors.As(err, &urlErr) {\n    // urlErr.Op == \"parse\": malformed URL — fix the query/endpoint\n}","preventionTips":["Strip control characters from model-provided queries","Validate the endpoint constant if it is configurable","Detect pre-canceled contexts before issuing requests","Unit-test search with adversarial query strings"],"tags":["http","network","search"],"backgroundTag":"invalid-url","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}