{"repo":"NousResearch/hermes-agent","public_id":"6879","display_label":"Dash vs dot notation in Anthropic model IDs across providers","summary":"Multiple providers (Copilot, OpenRouter, Nous) reject or misroute Anthropic model IDs when submitted in dash-notation (e.g. claude-opus-4-8) instead of dot-notation (e.g. claude-opus-4.8). The issues and accompanying fixes address normalization and aliasing of these ID formats across different provider integrations.","subsystem":"agent","counts":{"members":8,"open_issues":2,"open_issues_distinct":2,"open_prs":1,"closed_issues":1,"merged_prs":1,"attempt_prs":4},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":11561},"resolution":{"state":"partially_resolved","by":"pr-11561"},"freshness":{"state":"stale","last_verified_at":"2026-04-17T11:19:37+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.20428571428571426},"fix_edges":[{"issue_number":6879,"pr_number":11561,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":6879,"title":"[Bug]: Copilot provider rejects Claude model names in dash-notation (HTTP 400)","url":null,"state":"closed"},{"kind":"issue","number":45362,"title":"nous provider: dash-style model ids (anthropic/claude-sonnet-4-6) 404 where Nous needs dot-style (…4.6); catalog also lists chat-uncallable models (claude-fable-5) — opaque 'Couldn't find that, sorry.'","url":null,"state":"open"},{"kind":"issue","number":65152,"title":"_find_openrouter_slug fails on Anthropic-native dash IDs (e.g. claude-opus-4-8), causing HTTP 400 not a valid model ID","url":null,"state":"open"},{"kind":"pr","number":6743,"title":"fix(copilot): add dash-notation aliases for Claude model IDs","url":null,"state":"closed","close_reason":{"category":"merged_via","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":11561,"title":"fix(copilot): normalize vendor-prefixed and dash-notation model IDs (#6879)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/model_normalize.py","hermes_cli/models.py","tests/hermes_cli/test_model_normalize.py"],"fix_hunks":[{"file":"hermes_cli/model_normalize.py","hunks":["@@ -374,7 +374,26 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str:\n             return bare\n         return _dots_to_hyphens(bare)\n \n-    # --- Copilot: strip matching provider prefix, keep dots ---\n+    # --- Copilot / Copilot ACP: delegate to the Copilot-specific\n+    #     normalizer.  It knows about the alias table (vendor-prefix\n+    #     stripping for Anthropic/OpenAI, dash-to-dot repair for Claude)\n+    #     and live-catalog lookups.  Without this, vendor-prefixed or\n+    #     dash-notation Claude IDs survive to the Copilot API and hit\n+    #     HTTP 400 \"model_not_supported\".  See issue #6879.\n+    if provider in {\"copilot\", \"copilot-acp\"}:\n+        try:\n+            from hermes_cli.models import normalize_copilot_model_id\n+\n+            normalized = normalize_copilot_model_id(name)\n+            if normalized:\n+                return normalized\n+        except Exception:\n+            # Fall through to the generic strip-vendor behaviour below\n+            # if the Copilot-specific path is unavailable for any reason.\n+            pass\n+\n+    # --- Copilot / Copilot ACP / openai-codex fallback:\n+    #     strip matching provider prefix, keep dots ---\n     if provider in _STRIP_VENDOR_ONLY_PROVIDERS:\n         stripped = _strip_matching_provider_prefix(name, provider)\n         if stripped == name and name.startswith(\"openai/\"):"]},{"file":"hermes_cli/models.py","hunks":["@@ -1488,6 +1488,19 @@ def _fetch_github_models(api_key: [redacted:secret-assignment] = None, timeout: float = 5.0) ->\n     \"anthropic/claude-sonnet-4.6\": \"claude-sonnet-4.6\",\n     \"anthropic/claude-sonnet-4.5\": \"claude-sonnet-4.5\",\n     \"anthropic/claude-haiku-4.5\": \"claude-haiku-4.5\",\n+    # Dash-notation fallbacks: Hermes' default Claude IDs elsewhere use\n+    # hyphens (anthropic native format), but Copilot's API only accepts\n+    # dot-notation.  Accept both so users who configure copilot + a\n+    # default hyphenated Claude model don't hit HTTP 400\n+    # \"model_not_supported\".  See issue #6879.\n+    \"claude-opus-4-6\": \"claude-opus-4.6\",\n+    \"claude-sonnet-4-6\": \"claude-sonnet-4.6\",\n+    \"claude-sonnet-4-5\": \"claude-sonnet-4.5\",\n+    \"claude-haiku-4-5\": \"claude-haiku-4.5\",\n+    \"anthropic/claude-opus-4-6\": \"claude-opus-4.6\",\n+    \"anthropic/claude-sonnet-4-6\": \"claude-sonnet-4.6\",\n+    \"anthropic/claude-sonnet-4-5\": \"claude-sonnet-4.5\",\n+    \"anthropic/claude-haiku-4-5\": \"claude-haiku-4.5\",\n }\n \n "]},{"file":"tests/hermes_cli/test_model_normalize.py","hunks":["@@ -93,6 +93,59 @@ def test_copilot_preserves_dots(self, model, expected):\n         assert result == expected\n \n \n+# ── Copilot model-name normalization (issue #6879 regression) ──────────\n+\n+class TestCopilotModelNormalization:\n+    \"\"\"Copilot requires bare dot-notation model IDs.\n+\n+    Regression coverage for issue #6879 and the broken Copilot branch\n+    that previously left vendor-prefixed Anthropic IDs (e.g.\n+    ``anthropic/claude-sonnet-4.6``) and dash-notation Claude IDs (e.g.\n+    ``claude-sonnet-4-6``) unchanged, causing the Copilot API to reject\n+    the request with HTTP 400 \"model_not_supported\".\n+    \"\"\"\n+\n+    @pytest.mark.parametrize(\"model,expected\", [\n+        # Vendor-prefixed Anthropic IDs — prefix must be stripped.\n+        (\"anthropic/claude-opus-4.6\",   \"claude-opus-4.6\"),\n+        (\"anthropic/claude-sonnet-4.6\", \"claude-sonnet-4.6\"),\n+        (\"anthropic/claude-sonnet-4.5\", \"claude-sonnet-4.5\"),\n+        (\"anthropic/claude-haiku-4.5\",  \"claude-haiku-4.5\"),\n+        # Vendor-prefixed OpenAI IDs — prefix must be stripped.\n+        (\"openai/gpt-5.4\",              \"gpt-5.4\"),\n+        (\"openai/gpt-4o\",               \"gpt-4o\"),\n+        (\"openai/gpt-4o-mini\",          \"gpt-4o-mini\"),\n+        # Dash-notation Claude IDs — must be converted to dot-notation.\n+        (\"claude-opus-4-6\",             \"claude-opus-4.6\"),\n+        (\"claude-sonnet-4-6\",           \"claude-sonnet-4.6\"),\n+        (\"claude-sonnet-4-5\",           \"claude-sonnet-4.5\"),\n+        (\"claude-haiku-4-5\",            \"claude-haiku-4.5\"),\n+        # Combined: vendor-prefixed + dash-notation.\n+        (\"anthropic/claude-opus-4-6\",   \"claude-opus-4.6\"),\n+        (\"anthropic/claude-sonnet-4-6\", \"claude-sonnet-4.6\"),\n+        # Already-canonical inputs pass through unchanged.\n+        (\"claude-sonnet-4.6\",           \"claude-sonnet-4.6\"),\n+        (\"gpt-5.4\",                     \"gpt-5.4\"),\n+        (\"gpt-5-mini\",                  \"gpt-5-mini\"),\n+    ])\n+    def test_copilot_normalization(self, model, expected):\n+        assert normalize_model_for_provider(model, \"copilot\") == expected\n+\n+    @pytest.mark.parametrize(\"model,expected\", [\n+        (\"anthropic/claude-sonnet-4.6\", \"claude-sonnet-4.6\"),\n+        (\"claude-sonnet-4-6\",           \"claude-sonnet-4.6\"),\n+        (\"claude-opus-4-6\",             \"claude-opus-4.6\"),\n+        (\"openai/gpt-5.4\",              \"gpt-5.4\"),\n+    ])\n+    def test_copilot_acp_normalization(self, model, expected):\n+        \"\"\"Copilot ACP shares the same API expectations as HTTP Copilot.\"\"\"\n+        assert normalize_model_for_provider(model, \"copilot-acp\") == expected\n+\n+    def test_openai_codex_still_strips_openai_prefix(self):\n+        \"\"\"Regression: openai-codex must still strip the openai/ prefix.\"\"\"\n+        assert normalize_model_for_provider(\"openai/gpt-5.4\", \"openai-codex\") == \"gpt-5.4\"\n+\n+\n # ── Aggregator providers (regression) ──────────────────────────────────\n \n class TestAggregatorProviders:\n"]}]},{"kind":"pr","number":45405,"title":"fix(models): normalize Anthropic dash-style IDs to dot-style for aggregator providers","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#45367","number":45367}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":45532,"title":"fix: nous provider dash-vs-dot model ID format and uncallable models","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":65167,"title":"fix(models): resolve dash-form model IDs against dotted OpenRouter catalog (#65152)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"148a625ec","number":0},{"kind":"pr","value":"#65152","number":65152}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true}],"trust":{"default_tier":"verified","includes_suggested":false}}