{"record":{"id":"8de6d2d5b30b5a75","repo":"vxcontrol/pentagi","slug":"failed-to-do-request-v-8de6d2","errorCode":null,"errorMessage":"failed to do request: %v","messagePattern":"failed to do request: (.+?)","errorType":"exception","errorClass":"Retryable","httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/searchers/tavily.go","lineNumber":134,"sourceCode":"\t\tIncludeRawContent: true,\n\t\tMaxResults:        maxResults,\n\t}\n\treqBody, err := json.Marshal(reqPayload)\n\tif err != nil {\n\t\treturn \"\", Fatal(fmt.Errorf(\"failed to marshal request body: %v\", err))\n\t}\n\n\treq, err := http.NewRequest(http.MethodPost, tavilyURL, bytes.NewBuffer(reqBody))\n\tif err != nil {\n\t\treturn \"\", Fatal(fmt.Errorf(\"failed to build request: %v\", err))\n\t}\n\n\treq = req.WithContext(ctx)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", Retryable(fmt.Errorf(\"failed to do request: %v\", err), 0)\n\t}\n\tdefer resp.Body.Close()\n\n\treturn t.parseHTTPResponse(ctx, resp)\n}\n\nfunc (t *tavily) parseHTTPResponse(ctx context.Context, resp *http.Response) (string, error) {\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar respBody tavilySearchResult\n\t\tif err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {\n\t\t\treturn \"\", Fatal(fmt.Errorf(\"failed to decode response body: %v\", err))\n\t\t}\n\t\treturn t.buildTavilyResult(ctx, &respBody), nil\n\tcase http.StatusBadRequest:\n\t\treturn \"\", Fatal(fmt.Errorf(\"request is invalid\"))\n\tcase http.StatusUnauthorized:\n\t\treturn \"\", Fatal(fmt.Errorf(\"API key is wrong\"))","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/tavily.go#L116-L152","documentation":"Thrown in tavily.search when the HTTP POST to https://api.tavily.com/search fails at the transport level (client.Do returned an error), before any HTTP status is available. The searcher wraps the underlying net/http error as Retryable(err, 0) so the web_search orchestrator may retry with fallback engines. It means DNS failure, TCP connect failure, TLS failure, request cancellation (context), or a proxy problem — not a Tavily API rejection.","triggerScenarios":"client.Do(req) in search (tavily.go:132) returns err: network unreachable, DNS resolution failure of api.tavily.com, TLS handshake failure, proxy misconfiguration (system.GetHTTPClient with proxy settings), or ctx cancelled/timed out mid-request.","commonSituations":"No internet access or DNS failure in the Docker sandbox; corporate proxy or self-signed MITM cert (HTTPS_PROXY / custom CA) misconfigured; Tavily outage or regional block; request deadline exceeded because the search took too long; IPv6-only environment that cannot reach the API.","solutions":["Check basic connectivity from the same container: curl -v https://api.tavily.com/search — fix DNS/firewall/proxy if it fails.","If behind a proxy, verify HTTPS_PROXY/HTTP_PROXY env vars and that the proxy CA cert is trusted by the container.","If the context deadline is the cause, increase the caller's timeout for the web_search tool.","Check status.tavily.com or retry later if Tavily itself is down; ensure a fallback engine (e.g. DuckDuckGo) is configured in the web_search fallback chain.","Since the error is Retryable, verify the orchestrator retry policy is enabled so transient blips self-heal."],"exampleFix":"// before: cryptic transport failure inside the sandbox\nclient, err := system.GetHTTPClient(t.cfg)\n// after: fail fast with a clear config check and honor ctx deadline\nif t.cfg.TavilyAPIKey == \"\" { return \"\", ErrNotConfigured }\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()\nresp, err := client.Do(req)","handlingStrategy":"retry","validationCode":"// check egress before calling the engine\nreq, _ := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.tavily.com\", nil)\nif _, err := http.DefaultClient.Do(req); err != nil {\n    return fmt.Errorf(\"no egress to api.tavily.com: %w\", err)\n}","typeGuard":"func isRetryableTransportErr(err error) bool {\n    return errors.Is(err, context.DeadlineExceeded) ||\n        errors.Is(err, context.Canceled) ||\n        errors.Is(err, syscall.ECONNREFUSED) ||\n        errors.Is(err, syscall.EHOSTUNREACH) ||\n        errors.Is(err, io.EOF)\n}","tryCatchPattern":"result, err := searcher.Handle(ctx, req)\nif err != nil {\n    var rerr *RetryableError\n    if errors.As(err, &rerr) {\n        // back off and retry, then fall back to another engine\n    }\n    return err\n}","preventionTips":["Smoke-test egress (curl https://api.tavily.com) in the container image or entrypoint before enabling the engine.","Set an explicit request timeout instead of inheriting an unbounded caller context.","Validate proxy env vars and CA certs whenever the deployment changes.","Always configure at least one fallback engine in the web_search fallback chain."],"tags":["network","http-client","searchers","retryable","go"],"backgroundTag":"http-request-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}