xAI chat-proxy 422: forced web_search tool_choice rewritten to unsupported allowed_tools on grok-4.6 (follow-up to #5183)

Author: aPipixia123456Created Sep 17, 2026Updated Sep 17, 2026
LabelsFixed

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_search choice gets the same treatment #5184 gave image_generation: rewrite to a ModelToolChoice variant chat-proxy accepts (string "required").
  • An allowed_tools list that only names web_search is rewritten the same way, including when the client sent that shape directly.
  • Mixed allowed_tools lists produce a choice object chat-proxy accepts (drop the web_search entry 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, trace 20260918013304-35aeefedfe00d6a5-4ba3f560, upstream https://cli-chat-proxy.grok.com/v1/responses via xAI OAuth provider.
  • Downstream gateway retried the identical body 3x (use_channel equivalent: 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 for web_search, only the failing forced-choice path above.

Source: router-for-me/CLIProxyAPI