{"repo":"NousResearch/hermes-agent","public_id":"16179","display_label":"Ollama Cloud model IDs with :cloud/-cloud suffix handling","summary":"The members appear to share one theme: Ollama Cloud model IDs carrying a :cloud or -cloud suffix (e.g. kimi-k2.6:cloud), reported in one bug issue as producing wrong names and 400/404 errors. The other members are pull requests that propose stripping or sanitizing that suffix, including two with identical titles.","subsystem":"hermes_cli","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":19881,"merged_at":"2026-05-04T19:38:16+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-05-04T19:38:16+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":16179,"pr_number":19881,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-05-04T19:38:16+00:00"}],"members":[{"kind":"issue","number":16179,"title":"[Bug]: Ollama Cloud model list shows wrong names with :cloud / -cloud suffix (e.g. kimi-k2.6:cloud), causing 400/404","url":null,"state":"closed"},{"kind":"pr","number":16192,"title":"fix(models): strip :cloud/-cloud suffix from models.dev Ollama Cloud IDs","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#19881","number":19881}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":16196,"title":"fix(cli): implement self-healing sanitization for Ollama Cloud model IDs","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#19881","number":19881}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":19881,"title":"fix(models): strip :cloud/-cloud suffix from models.dev Ollama Cloud IDs","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/models.py","tests/hermes_cli/test_ollama_cloud_provider.py"],"fix_hunks":[{"file":"hermes_cli/models.py","hunks":["@@ -2906,6 +2906,19 @@ def fetch_api_models(\n _OLLAMA_CLOUD_CACHE_TTL = 3600  # 1 hour\n \n \n+def _strip_ollama_cloud_suffix(model_id: str) -> str:\n+    \"\"\"Strip :cloud / -cloud suffixes that models.dev appends to Ollama Cloud IDs.\n+\n+    The live API uses clean IDs (e.g. 'kimi-k2.6') while models.dev sometimes\n+    returns them as 'kimi-k2.6:cloud'. Normalising before the dedup merge\n+    prevents duplicate entries in the merged model list.\n+    \"\"\"\n+    for suffix in (\":cloud\", \"-cloud\"):\n+        if model_id.endswith(suffix):\n+            return model_id[: -len(suffix)]\n+    return model_id\n+\n+\n def _ollama_cloud_cache_path() -> Path:\n     \"\"\"Return the path for the Ollama Cloud model cache.\"\"\"\n     from hermes_constants import get_hermes_home","@@ -3001,9 +3014,10 @@ def fetch_ollama_cloud_models(\n                 seen.add(m)\n                 merged.append(m)\n         for m in mdev_models:\n-            if m and m not in seen:\n-                seen.add(m)\n-                merged.append(m)\n+            normalized = _strip_ollama_cloud_suffix(m)\n+            if normalized and normalized not in seen:\n+                seen.add(normalized)\n+                merged.append(normalized)\n         if merged:\n             _save_ollama_cloud_cache(merged)\n             return merged"]},{"file":"tests/hermes_cli/test_ollama_cloud_provider.py","hunks":["@@ -401,6 +401,103 @@ def test_get_provider(self):\n         assert pdef.transport == \"openai_chat\"\n \n \n+# ── Cloud Suffix Stripping ──\n+\n+class TestOllamaCloudSuffixStripping:\n+    \"\"\"models.dev appends :cloud / -cloud suffixes that the live API omits.\n+\n+    fetch_ollama_cloud_models() must normalise these before the dedup merge so\n+    users never see broken IDs like 'kimi-k2.6:cloud' in the model picker.\n+    \"\"\"\n+\n+    def test_strips_colon_cloud_suffix(self, tmp_path, monkeypatch):\n+        \"\"\":cloud suffix from models.dev is stripped before merge.\"\"\"\n+        from hermes_cli.models import fetch_ollama_cloud_models\n+\n+        monkeypatch.setenv(\"HERMES_HOME\", str(tmp_path))\n+        monkeypatch.delenv(\"OLLAMA_API_KEY\", raising=False)\n+\n+        mock_mdev = {\n+            \"ollama-cloud\": {\n+                \"models\": {\"kimi-k2.6:cloud\": {\"tool_call\": True}}\n+            }\n+        }\n+        with patch(\"agent.models_dev.fetch_models_dev\", return_value=mock_mdev):\n+            result = fetch_ollama_cloud_models(force_refresh=True)\n+\n+        assert \"kimi-k2.6\" in result\n+        assert \"kimi-k2.6:cloud\" not in result\n+\n+    def test_strips_dash_cloud_suffix(self, tmp_path, monkeypatch):\n+        \"\"\"-cloud suffix from models.dev is stripped before merge.\"\"\"\n+        from hermes_cli.models import fetch_ollama_cloud_models\n+\n+        monkeypatch.setenv(\"HERMES_HOME\", str(tmp_path))\n+        monkeypatch.delenv(\"OLLAMA_API_KEY\", raising=False)\n+\n+        mock_mdev = {\n+            \"ollama-cloud\": {\n+                \"models\": {\"qwen3-coder:480b-cloud\": {\"tool_call\": True}}\n+            }\n+        }\n+        with patch(\"agent.models_dev.fetch_models_dev\", return_value=mock_mdev):\n+            result = fetch_ollama_cloud_models(force_refresh=True)\n+\n+        assert \"qwen3-coder:480b\" in result\n+        assert \"qwen3-coder:480b-cloud\" not in result\n+\n+    def test_no_duplicate_when_live_clean_and_mdev_suffixed(self, tmp_path, monkeypatch):\n+        \"\"\"Live API returns clean ID; mdev has :cloud variant — result has exactly one entry.\"\"\"\n+        from hermes_cli.models import fetch_ollama_cloud_models\n+\n+        monkeypatch.setenv(\"HERMES_HOME\", str(tmp_path))\n+        monkeypatch.setenv(\"OLLAMA_API_KEY\", \"test-key\")\n+\n+        mock_mdev = {\n+            \"ollama-cloud\": {\n+                \"models\": {\n+                    \"kimi-k2.6:cloud\": {\"tool_call\": True},\n+                    \"glm-5.1:cloud\": {\"tool_call\": True},\n+                }\n+            }\n+        }\n+        with patch(\"hermes_cli.models.fetch_api_models\", return_value=[\"kimi-k2.6\", \"glm-5.1\"]), \\\n+             patch(\"agent.models_dev.fetch_models_dev\", return_value=mock_mdev):\n+            result = fetch_ollama_cloud_models(force_refresh=True)\n+\n+        assert result.count(\"kimi-k2.6\") == 1\n+        assert result.count(\"glm-5.1\") == 1\n+        assert \"kimi-k2.6:cloud\" not in result\n+        assert \"glm-5.1:cloud\" not in result\n+\n+    def test_unsuffixed_model_id_unchanged(self, tmp_path, monkeypatch):\n+        \"\"\"Model IDs without :cloud / -cloud suffix are passed through unchanged.\"\"\"\n+        from hermes_cli.models import fetch_ollama_cloud_models\n+\n+        monkeypatch.setenv(\"HERMES_HOME\", str(tmp_path))\n+        monkeypatch.delenv(\"OLLAMA_API_KEY\", raising=False)\n+\n+        mock_mdev = {\n+            \"ollama-cloud\": {\n+                \"models\": {\"nemotron-3-nano:30b\": {\"tool_call\": True}}\n+            }\n+        }\n+        with patch(\"agent.models_dev.fetch_models_dev\", return_value=mock_mdev):\n+            result = fetch_ollama_cloud_models(force_refresh=True)\n+\n+        assert \"nemotron-3-nano:30b\" in result\n+\n+    def test_strip_suffix_helper(self):\n+        \"\"\"Unit test for the _strip_ollama_cloud_suffix helper.\"\"\"\n+        from hermes_cli.models import _strip_ollama_cloud_suffix\n+\n+        assert _strip_ollama_cloud_suffix(\"kimi-k2.6:cloud\") == \"kimi-k2.6\"\n+        assert _strip_ollama_cloud_suffix(\"glm-5.1:cloud\") == \"glm-5.1\"\n+        assert _strip_ollama_cloud_suffix(\"qwen3-coder:480b-cloud\") == \"qwen3-coder:480b\"\n+        assert _strip_ollama_cloud_suffix(\"nemotron-3-nano:30b\") == \"nemotron-3-nano:30b\"\n+        assert _strip_ollama_cloud_suffix(\"\") == \"\"\n+\n+\n # ── Auxiliary Model ──\n \n class TestOllamaCloudAuxiliary:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}