Skip to content

Commit 3e34d62

Browse files
authored
Enable 1h prompt caching and tool search for Claude client (#208)
* Enable 1h prompt caching and tool search for Claude client Set ENABLE_PROMPT_CACHING_1H=1 and ENABLE_TOOL_SEARCH=true in the Claude settings env block, matching Isaac's config. The 1h cache depends on the extended-cache-ttl-2025-04-11 beta header, which Claude Code only attaches when experimental betas are enabled. ucode previously set CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1, which suppressed that header, so the flag is removed. Because deep_merge_dict preserves keys already in ucode-settings.json, removing the key from the written env is not enough for existing installs. Add a CLAUDE_REMOVED_ENV_KEYS list, stripped from the managed settings file on every launch, so the stale key never lingers. This only touches ucode's own managed file, so a user who wants experimental betas disabled can still set the flag in their own ~/.claude/settings.json. Keep build_agent_state in sync with the same flag change. Co-authored-by: Isaac * Set ENABLE_TOOL_SEARCH=1 and enable CLAUDE_CODE_USE_GATEWAY Co-authored-by: Isaac
1 parent 09d33ee commit 3e34d62

4 files changed

Lines changed: 57 additions & 6 deletions

File tree

src/ucode/agents/claude.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ def _resolve_web_search_model(state: dict) -> str | None:
9393
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
9494
"ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME",
9595
)
96+
# Env keys ucode used to write but no longer does; stripped from the managed
97+
# settings file on every launch so stale values never linger.
98+
CLAUDE_REMOVED_ENV_KEYS = ("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",)
9699
CLAUDE_TRACING_STOP_HOOK_SUFFIX = " autolog claude stop-hook"
97100
# Tracing is driven by an `mlflow autolog claude stop-hook` Stop hook, run by
98101
# the `mlflow` CLI on each session end. Pin to 3.11.x: 3.12 dropped the Unity
@@ -166,8 +169,13 @@ def render_overlay(
166169
env: dict[str, str] = {
167170
"ANTHROPIC_BASE_URL": base_url,
168171
"ANTHROPIC_CUSTOM_HEADERS": custom_headers,
169-
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
170172
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "900000",
173+
# 1h prompt caching needs the extended-cache-ttl beta header, which
174+
# Claude Code only sends when experimental betas are enabled — so we must
175+
# not set CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS (see CLAUDE_REMOVED_ENV_KEYS).
176+
"ENABLE_PROMPT_CACHING_1H": "1",
177+
"ENABLE_TOOL_SEARCH": "1",
178+
"CLAUDE_CODE_USE_GATEWAY": "1",
171179
}
172180
# Intentionally NOT setting ANTHROPIC_MODEL. Setting it produces a duplicate
173181
# catalog row in Claude Code's /model picker (e.g. "Opus 4.8 (1M context) ✓")
@@ -343,6 +351,11 @@ def write_tool_config(
343351
for key in CLAUDE_MANAGED_MODEL_ENV_KEYS:
344352
if key not in overlay_env:
345353
merged_env.pop(key, None)
354+
# deep_merge_dict keeps keys already in the file, so drop the ones ucode no
355+
# longer writes.
356+
if isinstance(merged_env, dict):
357+
for key in CLAUDE_REMOVED_ENV_KEYS:
358+
merged_env.pop(key, None)
346359
write_json_file(CLAUDE_SETTINGS_PATH, merged)
347360

348361
if web_search_model:

src/ucode/state.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ def build_agent_state(state: dict) -> dict[str, dict]:
153153
"env": {
154154
"ANTHROPIC_BASE_URL": base_urls.get("claude"),
155155
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": str(AUTH_REFRESH_INTERVAL_MS),
156-
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
156+
# Kept in sync with agents/claude.py render_overlay.
157+
"ENABLE_PROMPT_CACHING_1H": "1",
158+
"ENABLE_TOOL_SEARCH": "1",
159+
"CLAUDE_CODE_USE_GATEWAY": "1",
157160
},
158161
},
159162
"codex": {

tests/test_agent_claude.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,22 @@ def test_sets_custom_headers(self):
8080
overlay, _ = claude.render_overlay(WS, "s4")
8181
assert "x-databricks-use-coding-agent-mode" in overlay["env"]["ANTHROPIC_CUSTOM_HEADERS"]
8282

83-
def test_disables_experimental_betas(self):
83+
def test_does_not_disable_experimental_betas(self):
84+
# Would suppress the beta header 1h prompt caching needs.
8485
overlay, _ = claude.render_overlay(WS, "s4")
85-
assert overlay["env"]["CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS"] == "1"
86+
assert "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS" not in overlay["env"]
87+
88+
def test_enables_prompt_caching_1h(self):
89+
overlay, _ = claude.render_overlay(WS, "s4")
90+
assert overlay["env"]["ENABLE_PROMPT_CACHING_1H"] == "1"
91+
92+
def test_enables_tool_search(self):
93+
overlay, _ = claude.render_overlay(WS, "s4")
94+
assert overlay["env"]["ENABLE_TOOL_SEARCH"] == "1"
95+
96+
def test_enables_use_gateway(self):
97+
overlay, _ = claude.render_overlay(WS, "s4")
98+
assert overlay["env"]["CLAUDE_CODE_USE_GATEWAY"] == "1"
8699

87100
def test_sets_api_key_helper(self):
88101
overlay, _ = claude.render_overlay(WS, "s4")
@@ -366,6 +379,30 @@ def test_explicit_override_used_over_codex_models(self, monkeypatch):
366379
assert calls == [("register", WS, "explicit-model")]
367380

368381

382+
class TestWriteToolConfigStripsRemovedEnvKeys:
383+
"""Stale keys ucode no longer writes are dropped from the merged settings."""
384+
385+
def _patch(self, monkeypatch, existing, written):
386+
monkeypatch.setattr(claude, "backup_existing_file", lambda *a, **kw: True)
387+
monkeypatch.setattr(claude, "read_json_safe", lambda path: existing)
388+
monkeypatch.setattr(
389+
claude, "write_json_file", lambda path, payload: written.append(payload)
390+
)
391+
monkeypatch.setattr(claude, "save_state", lambda state: None)
392+
monkeypatch.setattr(claude, "_register_web_search_mcp", lambda *a, **kw: True)
393+
394+
def test_strips_stale_disable_experimental_betas(self, monkeypatch):
395+
existing = {"env": {"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1"}}
396+
written: list = []
397+
self._patch(monkeypatch, existing, written)
398+
state = {"workspace": WS, "codex_models": []}
399+
claude.write_tool_config(state, "databricks-claude-sonnet-4")
400+
assert "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS" not in written[0]["env"]
401+
assert written[0]["env"]["ENABLE_PROMPT_CACHING_1H"] == "1"
402+
assert written[0]["env"]["ENABLE_TOOL_SEARCH"] == "1"
403+
assert written[0]["env"]["CLAUDE_CODE_USE_GATEWAY"] == "1"
404+
405+
369406
class TestRegisterWebSearchMcp:
370407
def test_clears_existing_then_adds(self, monkeypatch):
371408
import ucode.mcp as mcp_mod

tests/test_e2e.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,6 @@ def test_launch_claude_per_model(
504504
"ANTHROPIC_MODEL": model_id,
505505
"ANTHROPIC_BASE_URL": base_url,
506506
"ANTHROPIC_API_KEY": e2e_token,
507-
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
508507
}
509508
cmd = claude.validate_cmd("claude")
510509
result = _run_agent(cmd, env=env, timeout=90)
@@ -571,7 +570,6 @@ def test_launch_claude_through_provider(
571570
"CLAUDE_CONFIG_DIR": str(config_dir),
572571
"ANTHROPIC_BASE_URL": build_tool_base_url("claude", e2e_workspace),
573572
"ANTHROPIC_API_KEY": e2e_token,
574-
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
575573
}
576574
result = _run_agent(claude.validate_cmd("claude"), env=env, timeout=90)
577575
combined = (result.stdout + result.stderr).strip()

0 commit comments

Comments
 (0)