diff --git a/src/ucode/agents/claude.py b/src/ucode/agents/claude.py index b5cc7207..8de3ea7f 100644 --- a/src/ucode/agents/claude.py +++ b/src/ucode/agents/claude.py @@ -55,7 +55,7 @@ sync_smart_routing_hooks, ) from ucode.state import MANAGED_OVERLAY_KEY, is_tool_managed, mark_tool_managed, save_state -from ucode.telemetry import agent_version, ucode_version +from ucode.telemetry import agent_version, ucode_release_version from ucode.tracing import tracing_env from ucode.ui import print_note, print_success, print_warning @@ -363,7 +363,7 @@ def render_overlay( # traffic to ucode. header_lines = [ "x-databricks-use-coding-agent-mode: true", - f"User-Agent: ucode/{ucode_version()} claude/{agent_version('claude')}", + f"User-Agent: ucode/{ucode_release_version()}", ] if provider: header_lines.append(f"{MODEL_PROVIDER_SERVICE_HEADER}: {provider}") diff --git a/src/ucode/telemetry.py b/src/ucode/telemetry.py index 639ddae9..0dc18ea8 100644 --- a/src/ucode/telemetry.py +++ b/src/ucode/telemetry.py @@ -25,6 +25,11 @@ def ucode_version() -> str: return "unknown" +def ucode_release_version() -> str: + """Return the package release version without local build metadata.""" + return ucode_version().split("+", 1)[0] + + @cache def agent_version(binary: str) -> str: """Return the agent CLI's reported version, or "unknown" on any failure. diff --git a/tests/test_agent_claude.py b/tests/test_agent_claude.py index 4a8a0ac1..eda66055 100644 --- a/tests/test_agent_claude.py +++ b/tests/test_agent_claude.py @@ -447,14 +447,20 @@ def test_managed_keys_include_env_entries(self): class TestRenderOverlayUserAgent: - def _ua(self, monkeypatch) -> str: - monkeypatch.setattr(claude, "ucode_version", lambda: "0.1.0") - monkeypatch.setattr(claude, "agent_version", lambda binary: "2.1.136") + def _ua(self, monkeypatch, claude_version: str = "2.1.136") -> str: + monkeypatch.setattr(claude, "ucode_release_version", lambda: "0.1.0") + monkeypatch.setattr(claude, "agent_version", lambda binary: claude_version) overlay, _ = claude.render_overlay(WS, "s4") return overlay["env"]["ANTHROPIC_CUSTOM_HEADERS"] def test_user_agent_present(self, monkeypatch): - assert "User-Agent: ucode/0.1.0 claude/2.1.136" in self._ua(monkeypatch) + assert "User-Agent: ucode/0.1.0" in self._ua(monkeypatch) + + def test_user_agent_does_not_change_when_claude_updates(self, monkeypatch): + user_agent = self._ua(monkeypatch, "2.1.272") + + assert user_agent == "x-databricks-use-coding-agent-mode: true\nUser-Agent: ucode/0.1.0" + assert "claude/" not in user_agent def test_existing_databricks_header_preserved(self, monkeypatch): assert "x-databricks-use-coding-agent-mode: true" in self._ua(monkeypatch) @@ -853,7 +859,7 @@ def test_managed_file_merges_anthropic_custom_headers(self, monkeypatch): } } self._patch(monkeypatch, private_writes, managed_writes, existing_managed_settings) - monkeypatch.setattr(claude, "ucode_version", lambda: "1.0") + monkeypatch.setattr(claude, "ucode_release_version", lambda: "1.0") monkeypatch.setattr(claude, "agent_version", lambda _binary: "2.0") state = {"workspace": WS, "codex_models": []} @@ -863,7 +869,7 @@ def test_managed_file_merges_anthropic_custom_headers(self, monkeypatch): merged_headers = json.loads(text)["env"]["ANTHROPIC_CUSTOM_HEADERS"] assert merged_headers.splitlines() == [ "X-Enterprise-Header: retain", # Preserved from existing managed settings. - "User-Agent: ucode/1.0 claude/2.0", # From ucode; overwrites existing. + "User-Agent: ucode/1.0", # From ucode; overwrites existing. "x-databricks-use-coding-agent-mode: true", # Newly added by ucode. ] diff --git a/tests/test_e2e_user_agent.py b/tests/test_e2e_user_agent.py index 55f2f1d7..4296f49b 100644 --- a/tests/test_e2e_user_agent.py +++ b/tests/test_e2e_user_agent.py @@ -23,7 +23,7 @@ import pytest -from ucode.telemetry import agent_version, ucode_version +from ucode.telemetry import agent_version, ucode_release_version, ucode_version def _require_binary(binary: str): @@ -126,7 +126,8 @@ def capture_server(): def _expected_ua(agent_name: str, binary: str) -> str: - return f"ucode/{ucode_version()} {agent_name}/{agent_version(binary)}" + version = ucode_release_version() if agent_name == "claude" else ucode_version() + return f"ucode/{version} {agent_name}/{agent_version(binary)}" def _assert_ua(req: _CapturedRequest, expected: str) -> None: diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index a60a7863..1a35b522 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -15,6 +15,13 @@ def test_returns_string(self): assert telemetry.ucode_version() != "" +class TestUcodeReleaseVersion: + def test_drops_local_build_metadata(self, monkeypatch): + monkeypatch.setattr(telemetry, "ucode_version", lambda: "0.1.0+172.g673cff4") + + assert telemetry.ucode_release_version() == "0.1.0" + + class TestAgentVersion: def setup_method(self): # The helper is @cache'd; clear between tests so each gets a clean run.