NousResearch/hermes-agent

OpenRouter credential pool exhaustion and env var fallback

Cluster 23452 · agent. Data as of

2 open items. Fix merged in #53906 on 28 Jun 2026. Check it covers your case.

The short answer

1open issue
1open PR
8items grouped
Known?
Yes. 8 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #53906. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #53906. That is separate from the fix event.
Still open
1 issue and 1 PR.

These issues and pull requests concern a bug where exhausted or stale OpenRouter credential pools block fallback to the OPENROUTER_API_KEY environment variable. Multiple fix attempts address the fallback priority logic in the OpenRouter credential selection path.

Open issues1

  • Issue#48829[Bug]: OpenRouter fallback can select stale pool credential before OPENROUTER_API_KEY

Open PRs1

  • PR#48395fix: prefer Hermes env key for OpenRouter fallback

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #53906 fix(auxiliary): fall back to OPENROUTER_API_KEY when credential pool exhausted

    merged to main

    files: agent/auxiliary_client.py, tests/agent/test_auxiliary_client.py

    Show the diff
    • agent/auxiliary_client.py
      @@ -1669,13 +1669,14 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
           pool_present, entry = _select_pool_entry("openrouter")
           if pool_present:
               or_key = explicit_api_key or _pool_runtime_api_key(entry)
      -        if not or_key:
      -            _mark_provider_unhealthy("openrouter", ttl=60)
      -            return None, None
      -        base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
      -        logger.debug("Auxiliary client: OpenRouter via pool")
      -        return OpenAI(api_key=[redacted:secret-assignment] base_url=base_url,
      -                       default_headers=build_or_headers()), model or _OPENROUTER_MODEL
      +        if or_key:
      +            base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
      +            logger.debug("Auxiliary client: OpenRouter via pool")
      +            return OpenAI(api_key=[redacted:secret-assignment] base_url=base_url,
      +                           default_headers=build_or_headers()), model or _OPENROUTER_MODEL
      +        # Pool exists but is exhausted (no usable runtime key) — fall through to
      +        # the OPENROUTER_API_KEY env-var path rather than failing outright.
      +        logger.debug("Auxiliary client: OpenRouter pool exhausted, trying OPENROUTER_API_KEY")
       
           or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")
           if not or_key:
    • tests/agent/test_auxiliary_client.py
      @@ -27,6 +27,9 @@
           _refresh_nous_recommended_model,
           _normalize_aux_provider,
           _try_payment_fallback,
      +    _try_openrouter,
      +    _OPENROUTER_MODEL,
      +    OPENROUTER_BASE_URL,
           _resolve_auto,
           _resolve_xai_oauth_for_aux,
           _CodexCompletionsAdapter,
      @@ -919,6 +922,35 @@ def test_explicit_openrouter_missing_env_keeps_not_set_warning(self, monkeypatch
                   for record in caplog.records
               )
       
      +    def test_try_openrouter_pool_exhausted_falls_back_to_env(self, monkeypatch):
      +        """Pool present but exhausted → fall through to OPENROUTER_API_KEY env var."""
      +        monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-env-fallback")
      +        with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \
      +             patch("agent.auxiliary_client.OpenAI") as mock_openai:
      +            mock_client = MagicMock(name="openrouter_client")
      +            mock_openai.return_value = mock_client
      +
      +            client, model = _try_openrouter()
      +
      +        assert client is mock_client
      +        assert model == _OPENROUTER_MODEL
      +        mock_openai.assert_called_once()
      +        assert mock_openai.call_args.kwargs["api_key"] == "sk-or-env-fallback"
      +        assert mock_openai.call_args.kwargs["base_url"] == OPENROUTER_BASE_URL
      +
      +    def test_try_openrouter_pool_exhausted_no_env_marks_unhealthy(self, monkeypatch):
      +        """Pool exhausted AND no env var → final failure marks provider unhealthy."""
      +        monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
      +        with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \
      +             patch("agent.auxiliary_client._mark_provider_unhealthy") as mock_mark, \
      +             patch("agent.auxiliary_client.OpenAI") as mock_openai:
      +            client, model = _try_openrouter()
      +
      +        assert client is None
      +        assert model is None
      +        mock_openai.assert_not_called()
      +        mock_mark.assert_called_once_with("openrouter", ttl=60)
      +
       class TestGetTextAuxiliaryClient:
           """Test the full resolution chain for get_text_auxiliary_client."""
       

4 PRs closed without merging

From the close comments: duplicate 1 · merged via another change 1 · other 2

  • #23495 fix(auxiliary): fall back to env when openrouter pool is exhausted · merged via another change → c56b39c, #53906
  • #23903 fix: fall through to OPENROUTER_API_KEY env var when credential pool is exhausted (#23452) · duplicate → #23495, #23905, #23904
  • #24720 fix(auxiliary): fallback to OPENROUTER_API_KEY env var when credential pool is exhausted · other → c56b39c, #53906, #23495
  • #48895 fix(agent): prefer OPENROUTER_API_KEY env over stale pool entries (#48829) · other
Closed issues · 1
  • Closed#23452Bug: _try_openrouter() returns (None,None) when credential pool is exhausted, blocking env var fallback
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.23: the share of item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#53906
Closed issues
1
Attempts
4 PRs closed without merging
Fix link checked
28 Jun 2026

For your agent

The same facts, typed.