{"repo":"NousResearch/hermes-agent","public_id":"25727","display_label":"Credential pool provider isolation fixes and misc patches","summary":"The majority of members address a bug where credential pools are not scoped to their provider, causing cross-provider credential mismatches during fallback and primary restore. A smaller number of unrelated fixes cover subprocess timeouts, event loop blocking, deprecated datetime usage, and async exception handling.","subsystem":"agent","counts":{"members":20,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":4,"merged_prs":2,"attempt_prs":14},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":56903},"resolution":{"state":"partially_resolved","by":"pr-56903,pr-63048"},"freshness":{"state":"verified","last_verified_at":"2026-07-12T10:00:54+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.09894736842105262},"fix_edges":[{"issue_number":52777,"pr_number":63048,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":56374,"pr_number":56903,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":56885,"pr_number":56903,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":25727,"title":"[Bug]: Fallback from Oauth provider to API key can cause credential pool desync","url":null,"state":"closed"},{"kind":"issue","number":52777,"title":"Bug: Credential pool checkout is provider-blind — causes cross-provider 403s","url":null,"state":"closed"},{"kind":"issue","number":56374,"title":"restore_primary_runtime() swaps in a cross-provider credential, sending the wrong model name to the wrong endpoint","url":null,"state":"closed"},{"kind":"issue","number":56885,"title":"Credential pool desync persists on v0.18.0 — regression of #25727 (closed as implemented-on-main)","url":null,"state":"closed"},{"kind":"pr","number":25733,"title":"fix: keep credential pool aligned across fallback runtime switches","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"2e181602a17e559532fb71ff5979948ab162c199","number":0},{"kind":"commit","value":"3fe16e3cd5e929f6983a2deece9cd58d0d77eef8","number":0},{"kind":"pr","value":"#25727","number":25727}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":52799,"title":"fix: gate credential pools by provider","url":null,"state":"closed","close_reason":{"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":55703,"title":"fix(agent): gate credential pools by provider","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#52799","number":52799},{"kind":"pr","value":"#52777","number":52777}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":56392,"title":"fix(agent): keep primary restore on matching credential pool","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"820a05257","number":0},{"kind":"pr","value":"#56903","number":56903},{"kind":"pr","value":"#56885","number":56885}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":56466,"title":"fix(agent): gate restore_primary_runtime credential pool by provider","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56392","number":56392},{"kind":"pr","value":"#56374","number":56374}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":56903,"title":"fix(agent): provider-match credential pool on primary restore (salvage #56392)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/agent_runtime_helpers.py","tests/run_agent/test_primary_runtime_restore.py"],"fix_hunks":[{"file":"agent/agent_runtime_helpers.py","hunks":["@@ -1184,11 +1184,38 @@ def restore_primary_runtime(agent) -> bool:\n         if pool is not None and pool.has_available():\n             entry = pool.select()\n             if entry is not None:\n+                entry_provider = str(getattr(entry, \"provider\", \"\") or \"\").strip().lower()\n+                primary_provider = str(rt.get(\"provider\") or \"\").strip().lower()\n+                entry_matches_primary = entry_provider == primary_provider\n+                # Custom endpoints all carry the generic ``custom`` provider on\n+                # the agent while the pool entry is keyed ``custom:<name>`` (see\n+                # CUSTOM_POOL_PREFIX). Resolve the primary's base_url to its\n+                # ``custom:<name>`` key via the canonical helper and compare\n+                # against the entry's key — this mirrors the sibling guard in\n+                # ``recover_with_credential_pool`` (see above) and correctly\n+                # disambiguates multiple custom providers that share one gateway\n+                # base_url. Fixes #56885.\n+                from agent.credential_pool import CUSTOM_POOL_PREFIX\n+                if (\n+                    primary_provider == \"custom\"\n+                    and entry_provider.startswith(CUSTOM_POOL_PREFIX)\n+                ):\n+                    entry_matches_primary = False\n+                    try:\n+                        from agent.credential_pool import get_custom_provider_pool_key\n+                        primary_base_url = str(rt.get(\"base_url\") or \"\").strip()\n+                        primary_key = (\n+                            get_custom_provider_pool_key(primary_base_url) or \"\"\n+                        ).strip().lower()\n+                        entry_matches_primary = bool(primary_key) and primary_key == entry_provider\n+                    except Exception:\n+                        entry_matches_primary = False\n+\n                 entry_key = (\n                     getattr(entry, \"runtime_api_key\", None)\n                     or getattr(entry, \"access_token\", \"\")\n                 )\n-                if entry_key:\n+                if entry_key and entry_matches_primary:\n                     # ``_swap_credential`` rebuilds the OpenAI/Anthropic client,\n                     # reapplies base-url-scoped headers, and carries the\n                     # accumulated base_url / OAuth-detection fixes (#33163).","@@ -1198,6 +1225,14 @@ def restore_primary_runtime(agent) -> bool:\n                         getattr(entry, \"id\", \"?\"),\n                         getattr(entry, \"label\", \"?\"),\n                     )\n+                elif entry_key:\n+                    logger.info(\n+                        \"Restore skipped pool entry %s (%s): provider %s does not match primary provider %s\",\n+                        getattr(entry, \"id\", \"?\"),\n+                        getattr(entry, \"label\", \"?\"),\n+                        entry_provider or \"?\",\n+                        primary_provider or \"?\",\n+                    )\n \n         # ── Reset fallback chain for the new turn ──\n         agent._fallback_activated = False"]},{"file":"tests/run_agent/test_primary_runtime_restore.py","hunks":["@@ -201,6 +201,179 @@ def test_restores_prompt_caching_flag(self):\n \n         assert agent._use_prompt_caching == original_caching\n \n+    def test_restore_skips_cross_provider_pool_entry(self):\n+        \"\"\"Restore must not swap in a fallback provider credential for the primary runtime.\"\"\"\n+\n+        class _Entry:\n+            provider = \"openrouter\"\n+            id = \"fallback-entry\"\n+            label = \"fallback\"\n+            runtime_api_key = \"[redacted:secret-assignment]\"\n+            runtime_base_url = \"https://openrouter.ai/api/v1\"\n+            access_token = \"[redacted:secret-assignment]\"\n+\n+        class _Pool:\n+            provider = \"openrouter\"\n+\n+            def has_available(self):\n+                return True\n+\n+            def select(self):\n+                return _Entry()\n+\n+        agent = _make_agent(\n+            provider=\"custom\",\n+            base_url=\"https://primary.example.com/v1\",\n+            fallback_model={\"provider\": \"openrouter\", \"model\": \"anthropic/claude-sonnet-4\"},\n+        )\n+        original_base_url = agent.base_url\n+        mock_client = _mock_resolve()\n+        with patch(\"agent.auxiliary_client.resolve_provider_client\", return_value=(mock_client, None)):\n+            agent._try_activate_fallback()\n+        agent._credential_pool = [redacted:secret-assignment]\n+        agent._swap_credential = [redacted:secret-assignment]\n+\n+        with patch(\"run_agent.OpenAI\", return_value=MagicMock()):\n+            result = agent._restore_primary_runtime()\n+\n+        assert result is True\n+        assert agent.provider == \"custom\"\n+        assert agent.base_url == original_base_url\n+        agent._swap_credential.assert_not_called()\n+\n+    def test_restore_keeps_primary_base_url_when_fallback_pool_attached(self):\n+        \"\"\"Issue #56885: plain-provider primary must not inherit a fallback\n+        provider's base_url via the restore-path pool reselect.\n+\n+        Repro: primary is openai-api/gpt-5.5, a transient failure falls back to\n+        deepseek and attaches deepseek's credential pool. On the next turn the\n+        restore reselect must NOT swap in the deepseek entry — otherwise the\n+        request goes out as model=gpt-5.5 to base_url=api.deepseek.com → 404.\n+        \"\"\"\n+\n+        class _DeepseekEntry:\n+            provider = \"deepseek\"\n+            id = \"dsk-1\"\n+            label = \"deepseek-key\"\n+            runtime_api_key = \"[redacted:secret-assignment]\"\n+            runtime_base_url = \"https://api.deepseek.com/v1\"\n+            base_url = \"https://api.deepseek.com/v1\"\n+            access_token = \"[redacted:secret-assignment]\"\n+\n+        class _DeepseekPool:\n+            provider = \"deepseek\"\n+\n+            def has_available(self):\n+                return True\n+\n+            def select(self):\n+                return _DeepseekEntry()\n+\n+        agent = _make_agent(\n+            provider=\"openai-api\",\n+            base_url=\"https://api.openai.com/v1\",\n+            fallback_model={\"provider\": \"deepseek\", \"model\": \"deepseek-v4-flash\"},\n+        )\n+        primary_base_url = agent.base_url\n+        primary_provider = agent.provider\n+        mock_client = _mock_resolve(base_url=\"https://api.deepseek.com/v1\")\n+        with patch(\n+            \"agent.auxiliary_client.resolve_provider_client\",\n+            return_value=(mock_client, None),\n+        ):\n+            agent._try_activate_fallback()\n+        # Fallback attached deepseek's pool; simulate it surviving into the next turn.\n+        agent._credential_pool = [redacted:secret-assignment]\n+        agent._swap_credential = [redacted:secret-assignment]\n+\n+        with patch(\"run_agent.OpenAI\", return_value=MagicMock()):\n+            result = agent._restore_primary_runtime()\n+\n+        assert result is True\n+        assert agent.provider == primary_provider\n+        assert agent.base_url == primary_base_url\n+        assert \"deepseek\" not in str(agent.base_url)\n+        agent._swap_credential.assert_not_called()\n+\n+    def test_restore_swaps_matching_custom_pool_entry(self):\n+        \"\"\"Custom primary + custom:<name> entry whose base_url resolves to the\n+        SAME custom key must swap (legitimate same-endpoint rotation).\"\"\"\n+\n+        class _Entry:\n+            provider = \"custom:myllm\"\n+            id = \"custom-entry\"\n+            label = \"myllm\"\n+            runtime_api_key = \"[redacted:secret-assignment]\"\n+            runtime_base_url = \"https://my-llm.example.com/v1\"\n+            access_token = \"[redacted:secret-assignment]\"\n+\n+        class _Pool:\n+            provider = \"custom:myllm\"\n+\n+            def has_available(self):\n+                return True\n+\n+            def select(self):\n+                return _Entry()\n+\n+        agent = _make_agent(provider=\"custom\", base_url=\"https://my-llm.example.com/v1\")\n+        agent._fallback_activated = True\n+        agent._credential_pool = [redacted:secret-assignment]\n+        agent._swap_credential = [redacted:secret-assignment]\n+\n+        with (\n+            patch(\n+                \"agent.credential_pool.get_custom_provider_pool_key\",\n+                return_value=\"custom:myllm\",\n+            ),\n+            patch(\"run_agent.OpenAI\", return_value=MagicMock()),\n+        ):\n+            result = agent._restore_primary_runtime()\n+\n+        assert result is True\n+        agent._swap_credential.assert_called_once()\n+\n+    def test_restore_skips_cross_endpoint_custom_pool_entry(self):\n+        \"\"\"Custom primary + custom:<name> entry whose base_url resolves to a\n+        DIFFERENT custom key must skip — two named custom providers sharing a\n+        gateway must not cross-contaminate.\"\"\"\n+\n+        class _Entry:\n+            provider = \"custom:otherllm\"\n+            id = \"other-entry\"\n+            label = \"otherllm\"\n+            runtime_api_key = \"[redacted:secret-assignment]\"\n+            runtime_base_url = \"https://my-llm.example.com/v1\"\n+            access_token = \"[redacted:secret-assignment]\"\n+\n+        class _Pool:\n+            provider = \"custom:otherllm\"\n+\n+            def has_available(self):\n+                return True\n+\n+            def select(self):\n+                return _Entry()\n+\n+        agent = _make_agent(provider=\"custom\", base_url=\"https://my-llm.example.com/v1\")\n+        agent._fallback_activated = True\n+        original_base_url = agent.base_url\n+        agent._credential_pool = [redacted:secret-assignment]\n+        agent._swap_credential = [redacted:secret-assignment]\n+\n+        with (\n+            patch(\n+                \"agent.credential_pool.get_custom_provider_pool_key\",\n+                return_value=\"custom:myllm\",  # primary resolves to a DIFFERENT key\n+            ),\n+            patch(\"run_agent.OpenAI\", return_value=MagicMock()),\n+        ):\n+            result = agent._restore_primary_runtime()\n+\n+        assert result is True\n+        assert agent.base_url == original_base_url\n+        agent._swap_credential.assert_not_called()\n+\n     def test_restore_survives_exception(self):\n         \"\"\"If client rebuild fails, the method returns False gracefully.\"\"\"\n         agent = _make_agent()\n"]}]},{"kind":"pr","number":56912,"title":"fix(agent): skip credential pool re-selection when pool provider mismatches primary","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"820a05257","number":0},{"kind":"commit","value":"b837f07dc","number":0},{"kind":"pr","value":"#56374","number":56374},{"kind":"pr","value":"#56392","number":56392},{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56392","number":56392},{"kind":"pr","value":"#56562","number":56562},{"kind":"pr","value":"#56374","number":56374},{"kind":"pr","value":"#56885","number":56885}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":58283,"title":"fix(computer_use): add timeout to subprocess.run in permissions grant","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"b837f07dc","number":0},{"kind":"pr","value":"#58253","number":58253},{"kind":"pr","value":"#58253","number":58253},{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56885","number":56885}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":58369,"title":"fix(gateway): use asyncio.sleep in start_gateway to avoid blocking event loop","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#36163","number":36163}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":58723,"title":"fix(mcp): close shared stderr log file handle at process exit","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885}],"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":58756,"title":"fix(bluebubbles): replace deprecated datetime.utcnow() with datetime.now(timezone.utc)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":58792,"title":"fix(main): add timeout to _run_logged_subprocess to prevent hung desktop build","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56885","number":56885}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":59555,"title":"fix(matrix): add return_exceptions=True to asyncio.gather in _dispatch_sync","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#57780","number":57780},{"kind":"pr","value":"#57643","number":57643},{"kind":"pr","value":"#56345","number":56345},{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56345","number":56345},{"kind":"pr","value":"#57643","number":57643},{"kind":"pr","value":"#57780","number":57780}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":59654,"title":"fix(bluebubbles): replace deprecated datetime.utcnow() with timezone-aware alternative","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":59863,"title":"fix(tools): use module-level logger in session_search_tool","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56885","number":56885},{"kind":"pr","value":"#56885","number":56885}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":63048,"title":"fix(auth): enforce credential pool provider boundaries","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/agent_init.py","agent/credential_pool.py","hermes_cli/runtime_provider.py","tests/agent/test_credential_pool_provider_boundary.py"],"fix_hunks":[{"file":"agent/agent_init.py","hunks":["@@ -412,13 +412,25 @@ def init_agent(\n     agent.skip_context_files = skip_context_files\n     agent.load_soul_identity = load_soul_identity\n     agent.pass_session_id = pass_session_id\n-    agent._credential_pool = [redacted:secret-assignment]\n     agent.log_prefix_chars = log_prefix_chars\n     agent.log_prefix = f\"{log_prefix} \" if log_prefix else \"\"\n     # Store effective base URL for feature detection (prompt caching, reasoning, etc.)\n     agent.base_url = base_url or \"\"\n     provider_name = provider.strip().lower() if isinstance(provider, str) and provider.strip() else None\n     agent.provider = provider_name or \"\"\n+    if credential_pool is not None:\n+        try:\n+            from agent.credential_pool import credential_pool_matches_provider\n+\n+            if not credential_pool_matches_provider(\n+                credential_pool,\n+                agent.provider,\n+                base_url=agent.base_url,\n+            ):\n+                credential_pool = None\n+        except Exception:\n+            credential_pool = None\n+    agent._credential_pool = [redacted:secret-assignment]\n     agent.acp_command = acp_command or command\n     agent.acp_args = list(acp_args or args or [])\n     if api_mode in {\"chat_completions\", \"codex_responses\", \"anthropic_messages\", \"bedrock_converse\", \"codex_app_server\"}:"]},{"file":"agent/credential_pool.py","hunks":["@@ -445,6 +445,44 @@ def get_pool_strategy(provider: str) -> str:\n     return STRATEGY_FILL_FIRST\n \n \n+def credential_pool_matches_provider(\n+    pool_or_provider: Any,\n+    provider: Optional[str],\n+    *,\n+    base_url: Optional[str] = None,\n+) -> bool:\n+    \"\"\"Return whether a pool belongs to the requested runtime provider.\n+\n+    Named custom endpoints intentionally use two identities: the live agent is\n+    ``custom`` while its pool is keyed ``custom:<name>``. Accept that pair only\n+    when the runtime base URL resolves to the exact same custom pool key.\n+    Empty string identities fail closed. Legacy pool adapters without a\n+    ``provider`` attribute remain compatible; production pools are scoped.\n+    \"\"\"\n+    raw_pool_provider = getattr(pool_or_provider, \"provider\", None)\n+    if raw_pool_provider is None:\n+        if isinstance(pool_or_provider, str):\n+            raw_pool_provider = pool_or_provider\n+        else:\n+            # Backward compatibility for lightweight/unscoped pool adapters.\n+            # Production CredentialPool instances always carry ``provider``;\n+            # old plugins and tests may expose only select()/has_credentials().\n+            return True\n+    pool_provider = str(raw_pool_provider or \"\").strip().lower()\n+    provider_norm = str(provider or \"\").strip().lower()\n+    if not pool_provider or not provider_norm:\n+        return False\n+    if pool_provider == provider_norm:\n+        return True\n+    if provider_norm != \"custom\" or not pool_provider.startswith(CUSTOM_POOL_PREFIX):\n+        return False\n+    try:\n+        matched_pool = get_custom_provider_pool_key(base_url or \"\")\n+    except Exception:\n+        return False\n+    return str(matched_pool or \"\").strip().lower() == pool_provider\n+\n+\n DEFAULT_MAX_CONCURRENT_PER_CREDENTIAL = 1\n \n "]},{"file":"hermes_cli/runtime_provider.py","hunks":["@@ -11,7 +11,13 @@\n logger = logging.getLogger(__name__)\n \n from hermes_cli import auth as auth_mod\n-from agent.credential_pool import CredentialPool, PooledCredential, get_custom_provider_pool_key, load_pool\n+from agent.credential_pool import (\n+    CredentialPool,\n+    PooledCredential,\n+    credential_pool_matches_provider,\n+    get_custom_provider_pool_key,\n+    load_pool,\n+)\n from agent.secret_scope import get_secret as _get_secret\n from hermes_cli.auth import (\n     AuthError,","@@ -1731,7 +1737,19 @@ def resolve_runtime_provider(\n                 if not pool_api_key or not _agent_key_is_usable(nous_state, min_ttl):\n                     logger.debug(\"Nous pool entry agent_key still unavailable, falling through to runtime resolution\")\n                     pool_api_key = \"\"\n-        if entry is not None and pool_api_key:\n+        if (\n+            entry is not None\n+            and pool_api_key\n+            and credential_pool_matches_provider(\n+                pool,\n+                provider,\n+                base_url=(\n+                    getattr(entry, \"runtime_base_url\", None)\n+                    or getattr(entry, \"base_url\", None)\n+                    or \"\"\n+                ),\n+            )\n+        ):\n             return _resolve_runtime_from_pool_entry(\n                 provider=provider,\n                 entry=entry,"]},{"file":"tests/agent/test_credential_pool_provider_boundary.py","hunks":["@@ -0,0 +1,64 @@\n+\"\"\"Credential pools must never cross provider or custom-endpoint boundaries.\"\"\"\n+\n+from types import SimpleNamespace\n+from unittest.mock import patch\n+\n+from agent.credential_pool import credential_pool_matches_provider\n+from hermes_cli import runtime_provider as rp\n+\n+\n+def test_provider_match_requires_exact_non_custom_identity():\n+    assert credential_pool_matches_provider(\"deepseek\", \"deepseek\")\n+    assert not credential_pool_matches_provider(\"openai-codex\", \"deepseek\")\n+    assert not credential_pool_matches_provider(\"\", \"deepseek\")\n+\n+\n+def test_custom_pool_match_is_scoped_by_endpoint():\n+    with patch(\n+        \"agent.credential_pool.get_custom_provider_pool_key\",\n+        return_value=\"custom:lab\",\n+    ):\n+        assert credential_pool_matches_provider(\n+            \"custom:lab\", \"custom\", base_url=\"https://lab.example/v1\"\n+        )\n+        assert not credential_pool_matches_provider(\n+            \"custom:other\", \"custom\", base_url=\"https://lab.example/v1\"\n+        )\n+\n+\n+def test_runtime_ignores_pool_loaded_for_different_provider(monkeypatch):\n+    entry = SimpleNamespace(\n+        provider=\"openai-codex\",\n+        access_token=\"[redacted:secret-assignment]\",\n+        runtime_api_key=\"[redacted:secret-assignment]\",\n+        runtime_base_url=\"https://chatgpt.com/backend-api/codex\",\n+        base_url=\"https://chatgpt.com/backend-api/codex\",\n+    )\n+    pool = SimpleNamespace(\n+        provider=\"openai-codex\",\n+        has_credentials=[redacted:secret-assignment] True,\n+        select=lambda: entry,\n+    )\n+    monkeypatch.setattr(rp, \"load_pool\", lambda _provider: pool)\n+    monkeypatch.setattr(rp, \"resolve_provider\", lambda *_a, **_kw: \"deepseek\")\n+    monkeypatch.setattr(\n+        rp,\n+        \"_get_model_config\",\n+        lambda: {\"provider\": \"deepseek\", \"default\": \"deepseek-chat\"},\n+    )\n+    monkeypatch.setattr(\n+        rp,\n+        \"resolve_api_key_provider_credentials\",\n+        lambda _provider: {\n+            \"provider\": \"deepseek\",\n+            \"api_key\": \"deepseek-key\",\n+            \"base_url\": \"https://api.deepseek.com/v1\",\n+            \"source\": \"env\",\n+        },\n+    )\n+\n+    resolved = rp.resolve_runtime_provider(requested=\"deepseek\")\n+\n+    assert resolved[\"provider\"] == \"deepseek\"\n+    assert resolved[\"api_key\"] == \"deepseek-key\"\n+    assert resolved[\"base_url\"] == \"https://api.deepseek.com/v1\"\n\\ No newline at end of file\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}