{"record":{"id":"13e8e55a833b011f","repo":"vxcontrol/pentagi","slug":"failed-to-do-request-w","errorCode":null,"errorMessage":"failed to do request: %w","messagePattern":"failed to do request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/searchers/google.go","lineNumber":96,"sourceCode":"\n\treturn result, nil\n}\n\n// classifyGoogleError maps a Google Custom Search failure to a retryable/fatal error.\n// A *googleapi.Error carries the upstream HTTP status (429/5xx retryable, 4xx fatal);\n// anything else is a transport-level failure, which may clear on retry.\nfunc classifyGoogleError(err error) error {\n\tvar gerr *googleapi.Error\n\tif errors.As(err, &gerr) {\n\t\treturn ClassifyHTTPStatus(gerr.Code, \"google search failed\")\n\t}\n\treturn Retryable(fmt.Errorf(\"google search failed: %w\", err), 0)\n}\n\nfunc (g *google) search(ctx context.Context, svc *customsearch.Service, query string, numResults int64) (string, error) {\n\tresp, err := svc.Cse.List().Context(ctx).Cx(g.cxKey()).Q(query).Lr(g.lrKey()).Num(numResults).Do()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to do request: %w\", err)\n\t}\n\n\treturn g.formatResults(resp), nil\n}\n\nfunc (g *google) formatResults(res *customsearch.Search) string {\n\tvar writer strings.Builder\n\tfor i, item := range res.Items {\n\t\twriter.WriteString(fmt.Sprintf(\"# %d. %s\\n\\n\", i+1, item.Title))\n\t\twriter.WriteString(fmt.Sprintf(\"## URL\\n%s\\n\\n\", item.Link))\n\t\twriter.WriteString(fmt.Sprintf(\"## Snippet\\n\\n%s\\n\\n\", item.Snippet))\n\t}\n\n\treturn writer.String()\n}\n\nfunc (g *google) newSearchService(ctx context.Context) (*customsearch.Service, error) {\n\tclient, err := system.GetHTTPClient(g.cfg)","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/google.go#L78-L114","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// diagnosing in a caller\nvar gerr *googleapi.Error\nif errors.As(err, &gerr) {\n    log.Printf(\"google status=%d body=%s\", gerr.Code, gerr.Body)\n}","handlingStrategy":"type-guard","validationCode":"// preflight quota/auth cheaply before search: one keyed request to the CSE endpoint\n// or validate GOOGLE_API_KEY/GOOGLE_CX_KEY presence at startup","typeGuard":"var gerr *googleapi.Error\nif errors.As(err, &gerr) {\n    switch {\n    case gerr.Code == 429: // quota: wait, don't hammer\n    case gerr.Code == 403: // auth/config: fix key or enable Custom Search API\n    }\n}","tryCatchPattern":"var gerr *googleapi.Error\nif errors.As(err, &gerr) {\n    log.Printf(\"google cse status=%d errors=%v\", gerr.Code, gerr.Errors)\n} else {\n    log.Printf(\"transport error: %v\", err)\n}","preventionTips":["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"],"tags":["google","customsearch","api-error","wrapped-error"],"backgroundTag":"google-search-request-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}