Environment
claude-code-proxy live grok-claude instance, 127.0.0.1:18765
Claude Code 2.1.220, VS Code extension
provider: grok
model: grok-4.6
Grok client version (proxy header): 0.2.93
Problem
Claude Code can declare Anthropic hosted web search with allowed_domains, blocked_domains, or user_location. Grok's hosted web_search does not have equivalent fields.
When any of those fields is non-null, claude-code-proxy returns HTTP 400 without calling the Grok agent:
API Error: 400 Grok hosted web search does not support allowed_domains
The design is meant to stop incompatible requests from reaching Grok. Claude Code, however, receives the proxy's HTTP 400, presents that error to the model as an error tool_result, and retries the search without allowed_domains. The retry is accepted and runs as an unrestricted web search.
The hard HTTP 400 therefore adds another turn, tokens, and latency, while the search still runs without the requested restriction. The intended protection from the 400 is not achieved.
How to reproduce
Reliable replay against 127.0.0.1:18765:
curl -s -X POST 'http://127.0.0.1:18765/v1/messages?beta=true' \
-H 'content-type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H 'authorization: Bearer unused' \
-d '{
"model": "grok-4.6",
"max_tokens": 64,
"stream": false,
"messages": [{"role": "user", "content": "What are LangChain Deep Agents LangGraph 2026 tools"}],
"tools": [{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 8,
"allowed_domains": ["langchain.com", "docs.langchain.com", "blog.langchain.com"]
}]
}'
Observed results
- The proxy rejects the hosted search containing
allowed_domains. Status: 400. No upstream Grok call is made.
- Claude Code presents the proxy error as a
tool_result for the original WebSearch tool use, with is_error: true.
- On the next assistant turn, the model issues two new
WebSearch calls for the same topic without allowed_domains.
- Those calls return search results.
Expected
A site-limited or location-limited hosted search should not consume a failed round only to be retried without its constraint on the next turn.
If the provider cannot enforce the constraint, the proxy should still be able to complete the search in one round. The existing hard 400 behavior should remain available as an opt-in legacy mode rather than being the default.
When the proxy does reject a request, the error should name every unsupported setting in that request -- allowed_domains, blocked_domains, user_location, or any combination of them -- instead of reporting only the first field it finds.
Proposal
CCP_SEARCH_CONSTRAINTS=hard|soft|warning
Default: soft.
| Value |
Behaviour |
hard |
Preserve the current proxy 400. In the observed Claude Code flow, the model then retries without the constraint. End effect: unrestricted search in two rounds. Legacy opt-in. |
soft |
Drop the unsupported fields, turn the constraints into instructions to the model, and send the hosted search. Best effort only. There is no guarantee that the model stays within the named sites. In testing, every listed source stayed in-domain. One round instead of two, saving tokens and latency. |
warning |
Drop the unsupported fields, log a warning, and send the hosted search without a prompt hint. This uses the same unrestricted search configuration as the retry after hard, but avoids the failed round and saves tokens and latency. |
In the observed retry path, hard and warning both lead to an unrestricted hosted-search request. warning avoids the failed round. soft is the only mode that still attempts to preserve the caller's requested sites.
The setting applies to providers that cannot enforce these Anthropic hosted-search options. Grok is the first such provider. The documentation should list the providers to which the setting applies.
max_uses remains dropped, as it is today.
Why soft is the default
Grok cannot enforce allowed_domains. With hard, Claude Code retries without domain restrictions, which does not preserve the original intent. With soft, the proxy turns each unsupported field into a direct instruction and appends it to the instructions field of the upstream Grok request. That field carries the system prompt.
This was tested 10 times with soft and domain restrictions, and 10 times without a domain list (warning / unrestricted). Both groups used the same query.
| Group |
Runs |
Total [inside, outside] |
In-domain share |
| soft (directive) |
10/10 |
[161, 0] |
100.0% |
| open (no domains) |
10/10 |
[147, 210] |
41.2% |
Every soft run listed in-domain sources only. Grok applied the instruction by adding site: filters to its own search queries. The unrestricted group returned more off-domain sources than in-domain ones.
The open group reaches 41.2% because the query names LangChain, so an unrestricted search finds the allowed domains anyway. This is why both groups must share one query.
soft remains a bias, not an enforcement mechanism. Grok can ignore the instruction. The measurement shows that it followed the instruction in all 10 runs.
Status: A fix is implemented and running in the fork AlexanderKugel/claude-code-proxy. The default is soft. Set CCP_SEARCH_CONSTRAINTS=hard to keep the current 400 behavior. A pull request to this repository will follow.
Environment
Problem
Claude Code can declare Anthropic hosted web search with
allowed_domains,blocked_domains, oruser_location. Grok's hostedweb_searchdoes not have equivalent fields.When any of those fields is non-null, claude-code-proxy returns HTTP 400 without calling the Grok agent:
The design is meant to stop incompatible requests from reaching Grok. Claude Code, however, receives the proxy's HTTP 400, presents that error to the model as an error
tool_result, and retries the search withoutallowed_domains. The retry is accepted and runs as an unrestricted web search.The hard HTTP 400 therefore adds another turn, tokens, and latency, while the search still runs without the requested restriction. The intended protection from the 400 is not achieved.
How to reproduce
Reliable replay against
127.0.0.1:18765:Observed results
allowed_domains. Status: 400. No upstream Grok call is made.tool_resultfor the originalWebSearchtool use, withis_error: true.WebSearchcalls for the same topic withoutallowed_domains.Expected
A site-limited or location-limited hosted search should not consume a failed round only to be retried without its constraint on the next turn.
If the provider cannot enforce the constraint, the proxy should still be able to complete the search in one round. The existing hard 400 behavior should remain available as an opt-in legacy mode rather than being the default.
When the proxy does reject a request, the error should name every unsupported setting in that request --
allowed_domains,blocked_domains,user_location, or any combination of them -- instead of reporting only the first field it finds.Proposal
Default:
soft.hardsoftwarninghard, but avoids the failed round and saves tokens and latency.In the observed retry path,
hardandwarningboth lead to an unrestricted hosted-search request.warningavoids the failed round.softis the only mode that still attempts to preserve the caller's requested sites.The setting applies to providers that cannot enforce these Anthropic hosted-search options. Grok is the first such provider. The documentation should list the providers to which the setting applies.
max_usesremains dropped, as it is today.Why
softis the defaultGrok cannot enforce
allowed_domains. Withhard, Claude Code retries without domain restrictions, which does not preserve the original intent. Withsoft, the proxy turns each unsupported field into a direct instruction and appends it to theinstructionsfield of the upstream Grok request. That field carries the system prompt.This was tested 10 times with
softand domain restrictions, and 10 times without a domain list (warning/ unrestricted). Both groups used the same query.Every
softrun listed in-domain sources only. Grok applied the instruction by addingsite:filters to its own search queries. The unrestricted group returned more off-domain sources than in-domain ones.The open group reaches 41.2% because the query names LangChain, so an unrestricted search finds the allowed domains anyway. This is why both groups must share one query.
softremains a bias, not an enforcement mechanism. Grok can ignore the instruction. The measurement shows that it followed the instruction in all 10 runs.Status: A fix is implemented and running in the fork
AlexanderKugel/claude-code-proxy. The default issoft. SetCCP_SEARCH_CONSTRAINTS=hardto keep the current 400 behavior. A pull request to this repository will follow.