vxcontrol/pentagi · warning · RetryableError

google search failed: %w

Error message

google search failed: %w

What it means

classifyGoogleError wraps any Google Custom Search failure that is NOT a *googleapi.Error into Retryable(fmt.Errorf("google search failed: %w", err)). This is the fallback branch: googleapi.Error means the server answered with an HTTP status (classified via ClassifyHTTPStatus); anything else — DNS failure, TLS error, connection refused, context deadline, JSON decoding — lands here as potentially transient.

Source

Thrown at backend/pkg/tools/searchers/google.go:90

			}),
		)

		obs.LogErrorOrCancel(logger, err, "failed to search in google")
		return "", classifyGoogleError(err)
	}

	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))
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Inspect the wrapped cause to distinguish network vs timeout vs TLS problems
  2. Verify container egress/proxy settings for www.googleapis.com:443
  3. Increase the request context timeout if deadline-exceeded is the cause
  4. Retry: the error is Retryable so the orchestrator's fallback strategy handles transient blips automatically
Defensive patterns

Strategy: retry

Validate before calling

// preflight DNS/egress
conn, err := net.DialTimeout("tcp", "www.googleapis.com:443", 5*time.Second)
if err != nil { // no egress: expect 'google search failed' retries
}

Type guard

var gerr *googleapi.Error
func isHTTPLevel(err error) bool { return errors.As(err, &googleapi.Error{} as any) != false && errors.As(err, &gerr) }
// simpler: func isTransportError(err error) bool { return !errors.As(err, new(*googleapi.Error)) }

Try / catch

result, err := googleSearcher.Handle(ctx, req)
if err != nil {
    if searchers.IsRetryable(err) {
        return fallbackSearcher.Handle(ctx, req) // transport blip; another engine serves the query
    }
    return "", err
}

Prevention

When it happens

Trigger: svc.Cse.List()...Do() returns a non-googleapi error: network unreachable, DNS resolution failure for www.googleapis.com, TLS handshake failure through the proxy, or ctx deadline exceeded mid-request.

Common situations: Backend container without egress to googleapis.com; corporate proxy requiring auth; short context timeouts canceling slow requests; intermittent DNS failures in Docker networks.

Related errors


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