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

  1. Unwrap with errors.As(err, &gerr): if *googleapi.Error, use gerr.Code/Body for the real cause
  2. For 403, check the API key is valid and Custom Search API is enabled; for 429, wait for quota reset or raise quota
  3. For transport errors, fix DNS/proxy/egress as with any network failure
  4. 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

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


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