vxcontrol/pentagi · error
failed to do request: %w
Error message
failed to do request: %w
What it means
google.search wraps any error from the Custom Search API Do() call as 'failed to do request: %w' before Handle classifies it. The actual cause is inside the wrapped chain: either a *googleapi.Error (server responded with 4xx/5xx, e.g. 403 quota/invalid key) or a transport error. This wrapper is the intermediate link that Handle's classifyGoogleError unwraps with errors.As.
Source
Thrown at backend/pkg/tools/searchers/google.go:96
return result, nil
}
// classifyGoogleError maps a Google Custom Search failure to a retryable/fatal error.
// A *googleapi.Error carries the upstream HTTP status (429/5xx retryable, 4xx fatal);
// anything else is a transport-level failure, which may clear on retry.
func classifyGoogleError(err error) error {
var gerr *googleapi.Error
if errors.As(err, &gerr) {
return ClassifyHTTPStatus(gerr.Code, "google search failed")
}
return Retryable(fmt.Errorf("google search failed: %w", err), 0)
}
func (g *google) search(ctx context.Context, svc *customsearch.Service, query string, numResults int64) (string, error) {
resp, err := svc.Cse.List().Context(ctx).Cx(g.cxKey()).Q(query).Lr(g.lrKey()).Num(numResults).Do()
if err != nil {
return "", fmt.Errorf("failed to do request: %w", err)
}
return g.formatResults(resp), nil
}
func (g *google) formatResults(res *customsearch.Search) string {
var writer strings.Builder
for i, item := range res.Items {
writer.WriteString(fmt.Sprintf("# %d. %s\n\n", i+1, item.Title))
writer.WriteString(fmt.Sprintf("## URL\n%s\n\n", item.Link))
writer.WriteString(fmt.Sprintf("## Snippet\n\n%s\n\n", item.Snippet))
}
return writer.String()
}
func (g *google) newSearchService(ctx context.Context) (*customsearch.Service, error) {
client, err := system.GetHTTPClient(g.cfg)View on GitHub (pinned to ea665308ba)
Solutions
- Unwrap with errors.As(err, &gerr): if *googleapi.Error, use gerr.Code/Body for the real cause
- For 403, check the API key is valid and Custom Search API is enabled; for 429, wait for quota reset or raise quota
- For transport errors, fix DNS/proxy/egress as with any network failure
- Do not modify googleAPIKeyTransport — WithHTTPClient replaces the SDK transport, so the key must be added as the query param there
Example fix
// diagnosing in a caller
var gerr *googleapi.Error
if errors.As(err, &gerr) {
log.Printf("google status=%d body=%s", gerr.Code, gerr.Body)
} Defensive patterns
Strategy: type-guard
Validate before calling
// preflight quota/auth cheaply before search: one keyed request to the CSE endpoint // or validate GOOGLE_API_KEY/GOOGLE_CX_KEY presence at startup
Type guard
var gerr *googleapi.Error
if errors.As(err, &gerr) {
switch {
case gerr.Code == 429: // quota: wait, don't hammer
case gerr.Code == 403: // auth/config: fix key or enable Custom Search API
}
} Try / catch
var gerr *googleapi.Error
if errors.As(err, &gerr) {
log.Printf("google cse status=%d errors=%v", gerr.Code, gerr.Errors)
} else {
log.Printf("transport error: %v", err)
} Prevention
- Monitor Custom Search quota (100 free queries/day) and alert near the limit
- Verify key/CSE-id pairing when rotating credentials
- Keep googleAPIKeyTransport intact so auth survives the WithHTTPClient override
When it happens
Trigger: svc.Cse.List().Context(ctx).Cx(cx).Q(query).Lr(lr).Num(n).Do() returns an error: invalid API key or CSE id (googleapi 400/403), daily quota exceeded (429), network failure, or canceled context.
Common situations: Exceeded free-tier 100 queries/day quota (googleapi 429); wrong GOOGLE_CX_KEY or key without Custom Search API enabled (403); egress blocked; the API key query-param transport dropping auth if modified (see googleAPIKeyTransport).
Related errors
- failed to create google search service: %w
- google search failed: %w
- failed to create google search service: %v
- failed to renew flow %d status: %w
- failed to load flow %d: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/13e8e55a833b011f.
Report an issue: GitHub.