# NousResearch/hermes-agent · explicit_api_key not forwarded to _try_openrouter

## Open now

- Nothing open on GitHub.

A bug where resolve_provider_client fails to pass explicit_api_key when calling _try_openrouter, and multiple fix attempts addressing this propagation gap.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #18768. Verify it addresses your case.
- **Recommended:** read PR #18768 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-18768`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #18338: \[Bug\]: resolve_provider_client ignores explicit_api_key when calling _try_openrouter()

## Prior & competing PRs

- **#18768: fix(auxiliary): propagate explicit_api_key to _try_openrouter (salvage #18341)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_auxiliary_client.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -1149,10 +1149,10 @@ def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]:
     
     
     
    -def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:
    +def _try_openrouter(explicit_api_key: str = None) -> Tuple[Optional[OpenAI], Optional[str]]:
         pool_present, entry = _select_pool_entry("openrouter")
         if pool_present:
    -        or_key = _pool_runtime_api_key(entry)
    +        or_key = explicit_api_key or _pool_runtime_api_key(entry)
             if not or_key:
                 return None, None
             base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
    ```

    ```diff
    @@ -1160,7 +1160,7 @@ def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:
             return OpenAI(api_key=[redacted:secret-assignment] base_url=base_url,
                            default_headers=_OR_HEADERS), _OPENROUTER_MODEL
     
    -    or_key = os.getenv("OPENROUTER_API_KEY")
    +    or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")
         if not or_key:
             return None, None
         logger.debug("Auxiliary client: OpenRouter")
    ```

    ```diff
    @@ -2053,9 +2053,9 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = "",
             return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode
                     else (client, final_model))
     
    -    # ── OpenRouter ───────────────────────────────────────────────────
    +    # ── OpenRouter ───────────────────────────────────────────
         if provider == "openrouter":
    -        client, default = _try_openrouter()
    +        client, default = _try_openrouter(explicit_api_key=[redacted:secret-assignment]
             if client is None:
                 logger.warning(
                     "resolve_provider_client: openrouter requested but %s",
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -1818,3 +1818,78 @@ def test_none_tools_unchanged(self):
                 provider="openai", model="gpt-4o", messages=[], tools=None,
             )
             assert "tools" not in kwargs
    +
    +
    [redacted:email](autouse=True)
    +def _clean_env(monkeypatch):
    +    """Strip provider env vars so each test starts clean."""
    +    for key in (
    +        "OPENROUTER_API_KEY", "OPENAI_BASE_URL", "OPENAI_API_KEY",
    +    ):
    +        monkeypatch.delenv(key, raising=False)
    +
    +
    +class TestOpenRouterExplicitApiKey:
    +    """Test that explicit_api_key is correctly propagated to _try_openrouter()."""
    +
    +    def test_resolve_provider_client_passes_explicit_api_key_to_openrouter(
    +        self, monkeypatch
    +    ):
    +        """
    +        When resolve_provider_client() is called with explicit_api_key for OpenRouter,
    +        the explicit key should be passed to the OpenAI client instead of falling back
    +        to OPENROUTER_API_KEY env var.
    +        """
    +        # Set up env var as fallback (should NOT be used when explicit_api_key is provided)
    +        monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key")
    +
    +        # Mock OpenAI to capture the api_key used
    +        mock_openai = MagicMock()
    +        mock_openai.return_value = MagicMock(name="openrouter-client")
    +
    +        with patch("agent.auxiliary_client.OpenAI", mock_openai):
    +            client, model = resolve_provider_client(
    +                provider="openrouter",
    +                explicit_api_key="[redacted:secret-assignment]",
    +            )
    +
    +            # Verify a client was created
    +            assert client is not None
    +            # Verify the explicit key was used, not the env var fallback
    +            mock_openai.assert_called_once()
    +            call_kwargs = mock_openai.call_args[1]
    +            assert call_kwargs["api_key"] == "explicit-pool-key", (
    +                f"Expected explicit_api_key to be passed, got: {call_kwargs['api_key']}"
    +            )
    +            assert call_kwargs["api_key"] != "env-fallback-key", (
    +                "Should NOT fall back to OPENROUTER_API_KEY when explicit_api_key is provided"
    +            )
    +
    +    def test_resolve_provider_client_without_explicit_api_key_falls_back_to_env(
    +        self, monkeypatch
    +    ):
    +        """
    +        When resolve_provider_client() is called WITHOUT explicit_api_key for OpenRouter,
    +        it should fall back to OPENROUTER_API_KEY env var.
    +        """
    +        # Set up env var as fallback (should be used when explicit_api_key is NOT provided)
    +        monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key")
    +
    +        # Mock OpenAI to capture the api_key used
    +        mock_openai = MagicMock()
    +        mock_openai.return_value = MagicMock(name="openrouter-client")
    +
    +        with patch("agent.auxiliary_client.OpenAI", mock_openai):
    +            client, model = resolve_provider_client(
    +                provider="openrouter",
    +                explicit_api_key=None,
    +            )
    +
    +            # Verify a client was created
    +            assert client is not None
    +            # Verify the env var fallback was used
    +            mock_openai.assert_called_once()
    +            call_kwargs = mock_openai.call_args[1]
    +            assert call_kwargs["api_key"] == "env-fallback-key", (
    +                f"Expected env fallback key to be used when explicit_api_key is None, got: {call_kwargs['api_key']}"
    +            )
    ```
- **#18341: fix(auxiliary): propagate explicit_api_key to _try_openrouter()**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#18768`
- **#18618: fix(auxiliary): forward explicit_api_key to _try_openrouter for credential pool fallback**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#18768`, `#18341`

## Fix status

- Merged to main in PR #18768 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-02`.
