diff --git a/studio/backend/core/inference/external_provider.py b/studio/backend/core/inference/external_provider.py
index ab6c5fc1d6..b9f8d459ba 100644
--- a/studio/backend/core/inference/external_provider.py
+++ b/studio/backend/core/inference/external_provider.py
@@ -71,14 +71,16 @@ def _normalize_stop_for_provider(
return None
-# Claude 4.7 (Opus/Sonnet/Haiku) removed temperature, top_p, and top_k —
-# the API returns 400 " is deprecated for this model" if any of
-# them is set to a non-default value. The "Sampling parameters removed"
-# section of the 4.7 release notes is the authoritative reference:
+# Claude 4.7 Opus removed temperature, top_p, and top_k — the API
+# returns 400 " is deprecated for this model" if any of them is
+# set to a non-default value. The "Sampling parameters removed" section
+# of the 4.7 release notes is the authoritative reference:
# https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7
-# 3.x and 4.5/4.6 still accept all three; match the 4-7 line strictly so
-# the knobs keep working on earlier families. The trailing -4-7[-.]/EOL
-# anchor keeps future versions (e.g. claude-opus-5) unaffected.
+# Only Opus shipped in the 4.7 generation (Sonnet stops at 4.6, Haiku at
+# 4.5 per https://platform.claude.com/docs/en/about-claude/models/overview),
+# so the regex is anchored to opus-4-7 only. 3.x and 4.5/4.6 still accept
+# all three knobs; the trailing -4-7[-.]/EOL anchor keeps future versions
+# (e.g. claude-opus-5) unaffected.
def _is_openai_family_cloud(base_url: Optional[str]) -> bool:
"""True iff ``base_url`` points at OpenAI cloud or Azure OpenAI Foundry.
@@ -107,9 +109,7 @@ def _is_openai_family_cloud(base_url: Optional[str]) -> bool:
return host == "api.openai.com" or host.endswith(".openai.azure.com")
-_ANTHROPIC_4_7_SAMPLING_REMOVED = re.compile(
- r"^claude-(?:opus|sonnet|haiku)-4-7(?:[-.]|$)"
-)
+_ANTHROPIC_4_7_SAMPLING_REMOVED = re.compile(r"^claude-opus-4-7(?:[-.]|$)")
_OPENAI_REASONING_SUMMARY_UNSUPPORTED = re.compile(r"^o3(?:[-.]|$)")
_OPENAI_REASONING_STATUSES = {"in_progress", "completed", "incomplete"}
diff --git a/studio/backend/tests/test_sampling_params_routing.py b/studio/backend/tests/test_sampling_params_routing.py
index 19fd9daf34..3671034f23 100644
--- a/studio/backend/tests/test_sampling_params_routing.py
+++ b/studio/backend/tests/test_sampling_params_routing.py
@@ -1105,14 +1105,18 @@ def test_anthropic_4_7_sampling_removed_regex_matches_expected_ids():
_ANTHROPIC_4_7_SAMPLING_REMOVED as RX,
)
+ # Only Opus shipped in the 4.7 generation per
+ # platform.claude.com/docs/en/about-claude/models/overview; Sonnet
+ # stops at 4.6 and Haiku at 4.5. Pin both directions explicitly so
+ # the regex never widens by accident.
should_match = [
"claude-opus-4-7",
- "claude-sonnet-4-7",
- "claude-haiku-4-7",
"claude-opus-4-7-20260418",
- "claude-sonnet-4-7.1",
+ "claude-opus-4-7.1",
]
should_not_match = [
+ "claude-sonnet-4-7",
+ "claude-haiku-4-7",
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-haiku-4-5",
diff --git a/studio/frontend/src/features/chat/provider-capabilities.ts b/studio/frontend/src/features/chat/provider-capabilities.ts
index d64415de1f..b4e60552ce 100644
--- a/studio/frontend/src/features/chat/provider-capabilities.ts
+++ b/studio/frontend/src/features/chat/provider-capabilities.ts
@@ -686,12 +686,14 @@ function isOpenAIReasoningModelId(modelId: string | null | undefined): boolean {
}
// Mirror of backend _ANTHROPIC_4_7_SAMPLING_REMOVED in
-// studio/backend/core/inference/external_provider.py:110. Claude 4.7
-// (Opus/Sonnet/Haiku) removed temperature, top_p, and top_k entirely;
-// surfacing the sliders would let the user move a control that the
-// backend silently strips. The trailing -4-7[-.]/EOL anchor keeps future
-// families (claude-opus-5 etc.) unaffected.
-const ANTHROPIC_4_7_SAMPLING_REMOVED_REGEX = /^claude-(?:opus|sonnet|haiku)-4-7(?:[-.]|$)/i;
+// studio/backend/core/inference/external_provider.py:110. Claude Opus
+// 4.7 removed temperature, top_p, and top_k entirely; surfacing the
+// sliders would let the user move a control that the backend silently
+// strips. Only Opus shipped in the 4.7 generation (Sonnet stops at 4.6,
+// Haiku at 4.5 per platform.claude.com/docs/en/about-claude/models/
+// overview), so the regex is opus-only. The trailing -4-7[-.]/EOL
+// anchor keeps future families (claude-opus-5 etc.) unaffected.
+const ANTHROPIC_4_7_SAMPLING_REMOVED_REGEX = /^claude-opus-4-7(?:[-.]|$)/i;
function isClaude47SamplingRemoved(modelId: string | null | undefined): boolean {
const normalized = modelId?.trim().toLowerCase() ?? "";
@@ -849,8 +851,9 @@ const DEFAULT_EXTERNAL_CAPABILITIES = OPENAI_COMPAT_BASE;
* gpt-3.5-turbo): full sampling surface (OPENAI_CHAT_CAPABILITIES).
* - openai + reasoning model (gpt-5.x, o1, o3, o4): restrictive
* (OPENAI_REASONING_CAPABILITIES).
- * - anthropic + claude-*-4-7: temperature/top_p/top_k stripped to
- * match the backend 400-avoidance regex.
+ * - anthropic + claude-opus-4-7: temperature/top_p/top_k stripped to
+ * match the backend 400-avoidance regex (Sonnet/Haiku 4.7 do not
+ * ship; only Opus does in the 4.7 generation).
* - deepseek + reasoning model (deepseek-reasoner / r1): hides
* temperature/top_p (silently ignored upstream).
*/