Skip to content

Commit 144b2ac

Browse files
authored
claude: disable built-in WebSearch via permissions.deny (not the no-op disabledTools key) (#188)
claude: disable built-in WebSearch via permissions.deny, not the no-op disabledTools key `ucode claude` tries to suppress Claude Code's built-in WebSearch tool by writing `"disabledTools": ["WebSearch"]` into the settings overlay. WebSearch declares Anthropic's hosted `web_search_20250305` server tool, which the Databricks gateway rejects with a 400 — so ucode disables it and routes web search through the `web_search` MCP server instead. But `disabledTools` is not a Claude Code settings key. Claude Code silently ignores unknown keys, so WebSearch stays advertised to the model: it calls WebSearch, hits the gateway 400, and only then falls back to the MCP tool — a wasted turn plus a 400 in the transcript on every web-search request. Confirmed on a staging managed session: the key was present in the merged --settings, yet native WebSearch was still called and 400'd. Use the real mechanism instead: a bare `permissions.deny` entry. A bare tool name in `deny` removes the tool from Claude's context entirely (unlike a scoped rule such as `WebSearch(*)`, which leaves it advertised and only blocks matching calls), so `web_search_20250305` is never advertised to the model nor sent to the gateway. The `web_search` MCP replacement is unchanged. Co-authored-by: Isaac
1 parent 446a24a commit 144b2ac

2 files changed

Lines changed: 19 additions & 9 deletions

File tree

src/ucode/agents/claude.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,20 @@ def render_overlay(
202202
}
203203
keys: list[list[str]] = [["apiKeyHelper"]] + [["env", k] for k in env]
204204

205-
# Disable Claude Code's built-in WebSearch (it routes through Anthropic's
206-
# hosted infra and fails through the Databricks gateway). The replacement
207-
# `web_search` MCP server is registered separately via the claude CLI.
205+
# Disable Claude Code's built-in WebSearch: it declares Anthropic's hosted
206+
# `web_search_20250305` server tool, which the Databricks gateway rejects
207+
# (HTTP 400: "Input tag 'web_search_20250305' ... does not match"), so the
208+
# model wastes a turn on it before falling back. A *bare* `permissions.deny`
209+
# entry removes the tool from Claude's context entirely, so it is never
210+
# advertised to the model nor sent to the gateway. (Claude Code has no
211+
# `disabledTools` setting — the `permissions` block is the only settings.json
212+
# mechanism for built-in tools; a bare tool name in `deny` drops it, whereas
213+
# a scoped rule like `WebSearch(*)` would leave it advertised.) The
214+
# replacement `web_search` MCP server is registered separately via the
215+
# claude CLI.
208216
if disable_web_search:
209-
overlay["disabledTools"] = ["WebSearch"]
210-
keys.append(["disabledTools"])
217+
overlay["permissions"] = {"deny": ["WebSearch"]}
218+
keys.append(["permissions", "deny"])
211219

212220
return overlay, keys
213221

tests/test_agent_claude.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,22 @@ def test_settings_overlay_never_includes_mcp_servers(self):
211211
assert "mcpServers" not in overlay
212212

213213
def test_disables_builtin_websearch_when_requested(self):
214+
# A bare `permissions.deny` entry removes the built-in WebSearch tool
215+
# from Claude's context (Claude Code has no `disabledTools` setting).
214216
overlay, _ = claude.render_overlay(WS, "s4", disable_web_search=True)
215-
assert overlay["disabledTools"] == ["WebSearch"]
217+
assert overlay["permissions"] == {"deny": ["WebSearch"]}
216218

217219
def test_no_disable_when_not_requested(self):
218220
overlay, _ = claude.render_overlay(WS, "s4", disable_web_search=False)
219-
assert "disabledTools" not in overlay
221+
assert "permissions" not in overlay
220222

221223
def test_managed_keys_include_disabled_tools_when_set(self):
222224
_, keys = claude.render_overlay(WS, "s4", disable_web_search=True)
223-
assert ["disabledTools"] in keys
225+
assert ["permissions", "deny"] in keys
224226

225227
def test_managed_keys_omit_disabled_tools_when_not_set(self):
226228
_, keys = claude.render_overlay(WS, "s4", disable_web_search=False)
227-
assert ["disabledTools"] not in keys
229+
assert ["permissions", "deny"] not in keys
228230

229231

230232
class TestWebSearchMcpEntry:

0 commit comments

Comments
 (0)