{"repo":"NousResearch/hermes-agent","public_id":"14618","display_label":"OpenRouter exhausted auxiliary key logging clarification","summary":"A bug was reported where an exhausted OpenRouter auxiliary API key was incorrectly logged as the key not being set. These members address clarifying the error message to accurately reflect the exhausted credential state.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":14739,"merged_at":"2026-04-23T21:04:31+00:00"},"resolution":{"state":"resolved","by":"pr-14739"},"freshness":{"state":"stale","last_verified_at":"2026-04-23T21:04:31+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":14618,"pr_number":14739,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-23T21:04:31+00:00"}],"members":[{"kind":"issue","number":14618,"title":"[Bug]: In auxiliary client, an exhausted OpenRouter key is logged as key not being set","url":null,"state":"closed"},{"kind":"pr","number":14646,"title":"fix(agent): clarify exhausted OpenRouter auxiliary credentials","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":14739,"title":"fix(agent): clarify exhausted OpenRouter auxiliary credentials (salvage #14646)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","tests/agent/test_auxiliary_client.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -916,6 +916,19 @@ def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:\n                    default_headers=_OR_HEADERS), _OPENROUTER_MODEL\n \n \n+def _describe_openrouter_unavailable() -> str:\n+    \"\"\"Return a more precise OpenRouter auth failure reason for logs.\"\"\"\n+    pool_present, entry = _select_pool_entry(\"openrouter\")\n+    if pool_present:\n+        if entry is None:\n+            return \"OpenRouter credential pool has no usable entries (credentials may be exhausted)\"\n+        if not _pool_runtime_api_key(entry):\n+            return \"OpenRouter credential pool entry is missing a runtime API key\"\n+    if not str(os.getenv(\"OPENROUTER_API_KEY\") or \"\").strip():\n+        return \"OPENROUTER_API_KEY not set\"\n+    return \"no usable OpenRouter credentials found\"\n+\n+\n def _try_nous(vision: bool = False) -> Tuple[Optional[OpenAI], Optional[str]]:\n     # Check cross-session rate limit guard before attempting Nous —\n     # if another session already recorded a 429, skip Nous entirely","@@ -1627,8 +1640,10 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = \"\"):\n     if provider == \"openrouter\":\n         client, default = _try_openrouter()\n         if client is None:\n-            logger.warning(\"resolve_provider_client: openrouter requested \"\n-                           \"but OPENROUTER_API_KEY not set\")\n+            logger.warning(\n+                \"resolve_provider_client: openrouter requested but %s\",\n+                _describe_openrouter_unavailable(),\n+            )\n             return None, None\n         final_model = _normalize_resolved_model(model or default, provider)\n         return (_to_async_client(client, final_model) if async_mode"]},{"file":"tests/agent/test_auxiliary_client.py","hunks":["@@ -447,6 +447,34 @@ def test_explicit_anthropic_api_key(self, monkeypatch):\n             adapter = client.chat.completions\n             assert adapter._is_oauth is False\n \n+    def test_explicit_openrouter_pool_exhausted_logs_precise_warning(self, monkeypatch, caplog):\n+        monkeypatch.delenv(\"OPENROUTER_API_KEY\", raising=False)\n+        with patch(\"agent.auxiliary_client._select_pool_entry\", return_value=(True, None)):\n+            with caplog.at_level(logging.WARNING, logger=\"agent.auxiliary_client\"):\n+                client, model = resolve_provider_client(\"openrouter\")\n+        assert client is None\n+        assert model is None\n+        assert any(\n+            \"credential pool has no usable entries\" in record.message\n+            for record in caplog.records\n+        )\n+        assert not any(\n+            \"OPENROUTER_API_KEY not set\" in record.message\n+            for record in caplog.records\n+        )\n+\n+    def test_explicit_openrouter_missing_env_keeps_not_set_warning(self, monkeypatch, caplog):\n+        monkeypatch.delenv(\"OPENROUTER_API_KEY\", raising=False)\n+        with patch(\"agent.auxiliary_client._select_pool_entry\", return_value=(False, None)):\n+            with caplog.at_level(logging.WARNING, logger=\"agent.auxiliary_client\"):\n+                client, model = resolve_provider_client(\"openrouter\")\n+        assert client is None\n+        assert model is None\n+        assert any(\n+            \"OPENROUTER_API_KEY not set\" in record.message\n+            for record in caplog.records\n+        )\n+\n class TestGetTextAuxiliaryClient:\n     \"\"\"Test the full resolution chain for get_text_auxiliary_client.\"\"\"\n \n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}