{"record":{"id":"06fa6ba93d83781d","repo":"vxcontrol/pentagi","slug":"request-is-invalid-06fa6b","errorCode":null,"errorMessage":"request is invalid","messagePattern":"request is invalid","errorType":"http","errorClass":"Fatal","httpStatus":400,"severity":"error","filePath":"backend/pkg/tools/searchers/tavily.go","lineNumber":150,"sourceCode":"\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\"))\n\tcase http.StatusForbidden:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the endpoint requested is hidden for administrators only\"))\n\tcase http.StatusNotFound:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the specified endpoint could not be found\"))\n\tcase http.StatusMethodNotAllowed:\n\t\treturn \"\", Fatal(fmt.Errorf(\"there need to try to access an endpoint with an invalid method\"))\n\tcase http.StatusTooManyRequests:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are requesting too many results\"), 0)\n\tcase http.StatusInternalServerError:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there had a problem with our server. try again later\"), 0)\n\tcase http.StatusBadGateway:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there was a problem with the server. Please try again later\"), 0)\n\tcase http.StatusServiceUnavailable:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are temporarily offline for maintenance. please try again later\"), 0)\n\tcase http.StatusGatewayTimeout:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are temporarily offline for maintenance. please try again later\"), 0)","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/tavily.go#L132-L168","documentation":"Thrown in tavily.parseHTTPResponse when the Tavily /search endpoint answers HTTP 400 Bad Request, mapped to the static message \"request is invalid\". Classified Fatal: the request body was rejected by the API as malformed or semantically invalid, so retrying the identical request will not help. The searcher deliberately discards Tavily's own error detail and returns this generic message.","triggerScenarios":"POST to api.tavily.com/search returns 400: empty or whitespace-only query, max_results out of the API's allowed range, invalid search_depth/topic values, or a malformed JSON body.","commonSituations":"Agent calls web_search with an empty or garbage query that flows through unchanged to Tavily; max_results configured above Tavily's cap; a Tavily API update tightening parameter validation (e.g. search_depth allowed values) while the client still sends \"advanced\" defaults that become invalid on a plan tier.","solutions":["Validate the Request before calling Tavily: reject empty/overlong queries and clamp MaxResults to the API's allowed range.","Log the outgoing request body (query, max_results, search_depth) when this fires to identify which parameter the API rejected.","Compare against the current Tavily /search API docs — parameter constraints may have changed.","Capture Tavily's 400 response body (it contains a detail message) instead of the static string for faster diagnosis.","If an agent generates the query, add prompt/schema-side guardrails so blank or malformed queries never reach the tool."],"exampleFix":"// before\nif strings.TrimSpace(req.Query) == \"\" { return \"\", ErrNotConfigured } // too late / wrong place\n// after: guard in Handle before building the payload\nq := strings.TrimSpace(req.Query)\nif q == \"\" { return \"\", Fatal(fmt.Errorf(\"query must not be empty\")) }\nmax := req.MaxResults\nif max <= 0 { max = 5 } else if max > 20 { max = 20 } // keep within Tavily limits","handlingStrategy":"validation","validationCode":"q := strings.TrimSpace(req.Query)\nif q == \"\" || len(q) > 400 {\n    return \"\", Fatal(fmt.Errorf(\"query must be 1-400 chars\"))\n}\nmax := req.MaxResults\nif max <= 0 { max = 5 } else if max > 20 { max = 20 }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, ErrFatalSearcher) && strings.Contains(err.Error(), \"request is invalid\") {\n        // log the outgoing query/max_results, correct the input, do not blind-retry\n    }\n    return err\n}","preventionTips":["Never pass agent-generated queries through unchecked — trim and bound them first.","Keep MaxResults within Tavily's documented range.","Diff your request parameters against the current Tavily API docs on upgrades.","Include Tavily's 400 response body in logs rather than swallowing it with the static message."],"tags":["http-400","validation","searchers","fatal","go"],"backgroundTag":"http-400-bad-request","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}