xAI chat-proxy 422: forced web_search tool_choice rewritten to unsupported allowed_tools on grok-4.6 (follow-up to #5183)
Is it a request payload issue? [x] Yes, this is a request payload issue. I am using a client/cURL to send a request payload, but I received an unexpected error. [ ] No, it's another issue.
If it's a request payload issue, you MUST know Our team doesn't have any GODs or ORACLEs or MIND READERs. Please make sure to attach the request log or curl payload.
Describe the bug
Follow-up to #5183 / #5184. The image_generation variant of this 422 was fixed by mapping the forced choice to the string "required", but the web_search variant is still broken on grok-4.6: a forced WebSearch choice is rewritten to an allowed_tools object that xAI chat-proxy rejects with HTTP 422.
Downstream (Anthropic /v1/messages) tool_choice:
{"name": "web_search", "type": "tool"}
with tools:
[{"allowed_domains": ["github.com"], "max_uses": 8, "name": "web_search", "type": "web_search_20250305"}]
CPA translates the upstream request (POST https://cli-chat-proxy.grok.com/v1/responses) tool_choice into:
{"type": "allowed_tools", "mode": "required", "tools": [{"type": "web_search"}]}
Upstream responds HTTP 422:
Failed to deserialize the JSON body into the target type: tool_choice: data did not match any variant of untagged enum ModelToolChoice
Deterministic: same body retried 3x, same 422 every time. Related: #4718 (same failure on grok-4.5 with the older {"type":"web_search"} shape, closed), #4758 (same symptom, open).
CLI Type xAI / Grok OAuth
Model Name
grok-4.6
LLM Client
codex-tui/0.153.3 via Anthropic /v1/messages?beta=true (Originator: codex-tui)
Request Information
CPA request log (request-log: true, CPA v7.3.4, trace 20260918013304-35aeefedfe00d6a5-4ba3f560). Secrets redacted; key/token values truncated by the logger itself.
Downstream request body (as received by CPA):
{"model": "grok-4.6", "system": [{"type": "text", "text": "Decline to give operational assistance for gaining unauthorized access to systems, networks, or data, or for defeating software licensing and protection controls. Say so briefly and offer a lawful alternative.\n\nOrdinary development, debugging, defensive security, and testing the caller is authorized to perform are all in scope. Credentials, keys, config files, and logs that appear in the caller's own working material are normal development context, not grounds to refuse."}, {"type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."}, {"type": "text", "text": "You are an assistant for performing a web search tool use"}], "messages": [{"role": "user", "content": [{"text": "Perform a web search for the query: CLIProxyAPI openai-compatibility convert claude thinking reasoning_content eceasy", "type": "text"}]}], "max_tokens": 64000, "stream": true, "tools": [{"allowed_domains": ["github.com"], "max_uses": 8, "name": "web_search", "type": "web_search_20250305"}], "output_config": {"effort": "high"}, "tool_choice": {"name": "web_search", "type": "tool"}, "thinking": {"type": "disabled"}}
Upstream request body (as sent by CPA to https://cli-chat-proxy.grok.com/v1/responses, reasoning.encrypted_content elided):
{"model": "grok-4.6", "instructions": "", "input": [{"type": "message", "role": "developer", "content": [{"type": "input_text", "text": "<system prompt texts>"}, {"type": "input_text", "text": "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."}, {"type": "input_text", "text": "You are an assistant for performing a web search tool use"}]}, {"type": "reasoning", "summary": [], "encrypted_content": "<elided>"}, {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "Perform a web search for the query: CLIProxyAPI openai-compatibility convert claude thinking reasoning_content eceasy"}]}], "tool_choice": {"type": "allowed_tools", "mode": "required", "tools": [{"type": "web_search"}]}, "parallel_tool_calls": true, "reasoning": {"effort": "low"}, "stream": true, "store": false, "include": ["reasoning.encrypted_content"], "tools": [{"type": "web_search", "filters": {"allowed_domains": ["github.com"]}}]}
Upstream response:
{"type": "error", "error": {"type": "invalid_request_error", "message": "{\"error\":\"Failed to deserialize the JSON body into the target type: tool_choice: data did not match any variant of untagged enum ModelToolChoice\"}"}}
Expected behavior
- A forced
web_searchchoice gets the same treatment #5184 gaveimage_generation: rewrite to aModelToolChoicevariant chat-proxy accepts (string"required"). - An
allowed_toolslist that only namesweb_searchis rewritten the same way, including when the client sent that shape directly. - Mixed
allowed_toolslists produce a choice object chat-proxy accepts (drop theweb_searchentry or equivalent), rather than forwarding a known-rejected shape.
Screenshots N/A
OS Type
- OS: Linux (server, container image; CPA v7.3.4) / macOS (client per
User-Agent: codex-tui/0.153.3 (Mac OS 26.5.1; arm64)) - Version: CPA v7.3.4
Additional context
- CPA version
v7.3.4, trace20260918013304-35aeefedfe00d6a5-4ba3f560, upstreamhttps://cli-chat-proxy.grok.com/v1/responsesvia xAI OAuth provider. - Downstream gateway retried the identical body 3x (
use_channelequivalent: same channel 3 attempts); all 3 returned this exact 422, so retry cannot recover — the translated shape must change. - #5183 documents control checks for
image_generation(omit/"required"/"auto"all 200); no equivalent control was run here forweb_search, only the failing forced-choice path above.
Source: router-for-me/CLIProxyAPI