From 214c2833146ca8d74ed80a3afdd4600659b5437d Mon Sep 17 00:00:00 2001 From: Lars Moan Date: Thu, 10 Sep 2026 07:54:27 +0000 Subject: [PATCH] fix: serve the newest Claude opus instead of pinning opus-4-8 Discovery swaps opus-5 out of the opus family slot when the workspace also serves opus-4-8. The swap exists so smart routing's frozen CLAUDE_ROUTE_ARMS menu finds its opus arm. Smart routing v2 offers the whole discovered catalog to the router, and no production caller reaches the v1 arm check, so the swap only downgrades every Claude Code and OpenCode user on such a workspace. Closes #382. --- src/ucode/databricks.py | 22 ---------------------- tests/test_databricks.py | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/ucode/databricks.py b/src/ucode/databricks.py index 49dba37f2..98cd6e826 100644 --- a/src/ucode/databricks.py +++ b/src/ucode/databricks.py @@ -1783,20 +1783,6 @@ def discover_claude_models_unbucketed(workspace: str, token: str) -> tuple[list[ return [m for m in ids if "claude-" in m.lower()], None -def _prefer_opus_4_8(models: dict[str, str], all_ids: list[str]) -> None: - """Swap the opus slot to claude-opus-4-8 when it's available. - - Discovery picks the newest opus (opus-5) but smart routing's - CLAUDE_ROUTE_ARMS require claude-opus-4-8. Pin to 4-8 when both - exist so the routing availability check passes. - """ - opus = models.get("opus") - if opus and "claude-opus-5" in opus: - opus_48 = next((m for m in all_ids if "claude-opus-4-8" in m), None) - if opus_48: - models["opus"] = opus_48 - - def discover_model_services( workspace: str, token: str ) -> tuple[dict[str, str], list[str], list[str], list[str], str | None]: @@ -1827,12 +1813,6 @@ def discover_model_services( ) if candidates: claude_models[family] = candidates[0] - # Smart routing's CLAUDE_ROUTE_ARMS require claude-opus-4-8, but the - # newest-wins sort above picks opus-5 when both exist — making the - # routing availability check fail. Pin opus-4-8 when it's available so - # routing works with the current task_v2 router. Revert to - # newest-wins once the router accepts opus-5 (PR databricks-eng/universe#2365446). - _prefer_opus_4_8(claude_models, ids) codex_models = sorted([m for m in ids if "gpt-" in m], key=model_version_sort_key) gemini_models = sorted([m for m in ids if "gemini-" in m], key=model_version_sort_key) @@ -2940,8 +2920,6 @@ def discover_claude_models(workspace: str, token: str) -> tuple[dict[str, str], ) if candidates: result[family] = candidates[0] - # Same opus-4-8 pin as discover_model_services — see comment there. - _prefer_opus_4_8(result, raw_ids) if result: return result, None if not raw_ids: diff --git a/tests/test_databricks.py b/tests/test_databricks.py index 20301da3b..3c8942851 100644 --- a/tests/test_databricks.py +++ b/tests/test_databricks.py @@ -243,6 +243,22 @@ def test_buckets_system_ai_claude_models(self, monkeypatch): "sonnet": "system.ai.claude-sonnet-4-6", } + def test_opus_5_wins_over_opus_4_8(self, monkeypatch): + # The opus slot is the newest opus the workspace serves. ucode used to swap opus-5 out for + # opus-4-8 to satisfy a frozen smart-routing arm menu, which downgraded every user. + payload = { + "data": [ + {"id": "databricks-claude-opus-4-8"}, + {"id": "databricks-claude-opus-5"}, + ] + } + monkeypatch.setattr(db_mod, "_http_get_json", lambda url, token, **kwargs: (payload, None)) + + models, reason = db_mod.discover_claude_models(WS, "token") + + assert reason is None + assert models["opus"] == "databricks-claude-opus-5" + def test_buckets_fable_family(self, monkeypatch): payload = { "data": [ @@ -320,6 +336,24 @@ def test_buckets_families_by_name(self, monkeypatch): "system.ai.kimi-k2-7-code", ] + def test_opus_5_wins_over_opus_4_8(self, monkeypatch): + # Same regression as the AI Gateway path: the opus slot is the newest opus, not a pinned + # opus-4-8. + payload = { + "model_services": [ + _model_service("system.ai.claude-opus-4-8"), + _model_service("system.ai.claude-opus-5"), + ] + } + monkeypatch.setattr( + db_mod, "_http_get_json", lambda url, token, timeout=10: (payload, None) + ) + + claude, _codex, _gemini, _oss, reason = db_mod.discover_model_services(WS, "token") + + assert reason is None + assert claude == {"opus": "system.ai.claude-opus-5"} + def test_oss_allowlist_drops_unsupported_families(self, monkeypatch): # Only explicitly supported chat families are retained. payload = { @@ -2707,9 +2741,8 @@ def test_the_two_discovery_helpers_share_one_walk(self, monkeypatch): claude, _codex, _gemini, _oss, _reason = db_mod.discover_model_services(WS, "tok") unbucketed, _ = db_mod.discover_claude_models_unbucketed(WS, "tok") assert calls["n"] == 1 - # Both views still come back intact: newest-per-family (pinned to opus-4-8 - # for smart-routing compatibility by _prefer_opus_4_8), and the full list. - assert claude["opus"] == "system.ai.claude-opus-4-8" + # Both views still come back intact: newest-per-family, and the full list. + assert claude["opus"] == "system.ai.claude-opus-5" assert unbucketed == ["system.ai.claude-opus-4-8", "system.ai.claude-opus-5"] def test_use_cache_false_forces_a_fresh_walk(self, monkeypatch):