NousResearch/hermes-agent

Agent provider headers missing or incorrect intermittently

Cluster 61099. Data as of

Nothing open on GitHub. Fix merged in #61732 on 10 Jul 2026. Check it covers your case.

The short answer

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

These issues and fixes relate to HTTP provider headers (such as OpenRouter HTTP-Referer) not being correctly set or reapplied for agent requests, particularly after model switches.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

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

  • #61732 fix(agent): reapply provider headers after model switch (#61099)

    merged to main

    files: agent/agent_runtime_helpers.py, scripts/release.py, tests/run_agent/test_switch_model_reapplies_headers.py

    Show the diff
    • agent/agent_runtime_helpers.py
      @@ -1953,6 +1953,11 @@ def switch_model(agent, new_model, new_provider, api_key='', base_url='', api_mo
                   _sm_timeout = get_provider_request_timeout(agent.provider, agent.model)
                   if _sm_timeout is not None:
                       agent._client_kwargs["timeout"] = _sm_timeout
      +            # Reapply provider-specific headers (e.g. OpenRouter HTTP-Referer,
      +            # X-Title) that were lost when _client_kwargs was rebuilt from
      +            # scratch.  Without this, model switches clear attribution headers
      +            # and OpenRouter logs show "Unknown" for subsequent requests.
      +            agent._apply_client_headers_for_base_url(effective_base)
                   agent.client = agent._create_openai_client(
                       dict(agent._client_kwargs),
                       reason="switch_model",
    • scripts/release.py
      @@ -55,6 +55,7 @@
           "[redacted:email]": "isheng-eqi",  # PR #59428 salvage (cron: reject past one-shot timestamps in update_job fallback + resume_job; #59395). Also PR #59446 salvage (cron: advance one-shot next_run_at before dispatch so concurrent gateway+desktop schedulers can't double-execute; #59229).
           "[redacted:email]": "derek2000139",  # PR #57838 salvage (desktop/windows: pre-write update marker before quit dwell so the renderer's waitForUpdateToFinish gate parks instead of respawning a backend that re-locks venv .pyd files mid-update)
           "[email protected]": "AndreasHiltner",  # PR #56854 salvage (gateway: route multiplex profile responses through the profile's own adapter — 53-site _adapter_for_source sweep)
      +    "[email protected]": "AlexFucuson9",  # PR #61347 salvage (agent: reapply provider headers after model switch; #61099)
           "[email protected]": "allenliang2022",  # PR #56932 test coverage folded into #56909 salvage (408 → retryable timeout)
           "[redacted:email]": "ManniBr",  # PR #57417 partial salvage (gateway: fail-closed adapter resolution for unregistered secondary profiles)
           "[redacted:email]": "Umi4Life",  # PR #47377 salvage (agent: emit one-shot fallback switch notice on successful fallback so gateway users see model/provider change; #35419)
    • tests/run_agent/test_switch_model_reapplies_headers.py
      @@ -0,0 +1,100 @@
      +"""Regression tests for #61099: switch_model must reapply provider-specific
      +default headers when it rebuilds _client_kwargs from scratch.
      +
      +Without _apply_client_headers_for_base_url() in the rebuild path, a /model
      +switch drops OpenRouter attribution headers (HTTP-Referer / X-Title → logs
      +show "Unknown") and, worse, functional headers like Kimi's User-Agent
      +sentinel (403 without it).
      +"""
      +
      +from unittest.mock import MagicMock, patch
      +
      +from run_agent import AIAgent
      +from agent.context_compressor import ContextCompressor
      +
      +
      +def _make_agent(provider="copilot", base_url="https://api.githubcopilot.com") -> AIAgent:
      +    """Minimal AIAgent with a context_compressor, skipping __init__."""
      +    agent = AIAgent.__new__(AIAgent)
      +
      +    agent.model = "claude-opus-4.8"
      +    agent.provider = provider
      +    agent.base_url = base_url
      +    agent.api_key = "[redacted:secret-assignment]"
      +    agent.api_mode = "chat_completions"
      +    agent.client = MagicMock()
      +    agent.quiet_mode = True
      +    agent._config_context_length = None
      +    agent._client_kwargs = {"api_key": "[redacted:secret-assignment]", "base_url": base_url}
      +
      +    compressor = ContextCompressor(
      +        model=agent.model,
      +        threshold_percent=0.50,
      +        base_url=base_url,
      +        api_key="[redacted:secret-assignment]",
      +        provider=provider,
      +        quiet_mode=True,
      +        config_context_length=None,
      +    )
      +    agent.context_compressor = compressor
      +    agent._primary_runtime = {}
      +
      +    return agent
      +
      +
      +@patch("agent.model_metadata.get_model_context_length", return_value=131_072)
      +def test_switch_to_openrouter_reapplies_attribution_headers(mock_ctx_len):
      +    """Switching to an openrouter.ai base_url must attach the OpenRouter
      +    attribution headers (HTTP-Referer / X-Title) to the rebuilt client
      +    kwargs — not ship a bare api_key+base_url client (#61099)."""
      +    agent = _make_agent(provider="copilot", base_url="https://api.githubcopilot.com")
      +
      +    agent.switch_model(
      +        "deepseek/deepseek-chat",
      +        "openrouter",
      +        api_key="[redacted:secret-assignment]",
      +        base_url="https://openrouter.ai/api/v1",
      +    )
      +
      +    headers = agent._client_kwargs.get("default_headers") or {}
      +    assert "HTTP-Referer" in headers
      +    assert headers.get("X-Title")
      +
      +
      +@patch("agent.model_metadata.get_model_context_length", return_value=131_072)
      +def test_switch_to_kimi_reapplies_user_agent_sentinel(mock_ctx_len):
      +    """Kimi requires a User-Agent sentinel; a switch to api.kimi.com must
      +    carry it or every request 403s."""
      +    agent = _make_agent(provider="openrouter", base_url="https://openrouter.ai/api/v1")
      +
      +    agent.switch_model(
      +        "kimi-k2",
      +        "kimi",
      +        api_key="[redacted:secret-assignment]",
      +        base_url="https://api.kimi.com/v1",
      +    )
      +
      +    headers = agent._client_kwargs.get("default_headers") or {}
      +    assert headers.get("User-Agent", "").startswith("claude-code/")
      +
      +
      +@patch("agent.model_metadata.get_model_context_length", return_value=131_072)
      +def test_switch_away_from_headered_provider_clears_stale_headers(mock_ctx_len):
      +    """Switching FROM a headered provider TO one with no URL-specific headers
      +    must not carry the old provider's headers along."""
      +    agent = _make_agent(provider="openrouter", base_url="https://openrouter.ai/api/v1")
      +    agent._client_kwargs["default_headers"] = {
      +        "HTTP-Referer": "https://hermes-agent.nousresearch.com",
      +        "X-Title": "Hermes Agent",
      +    }
      +
      +    agent.switch_model(
      +        "MiniMax-M3",
      +        "custom:minimax",
      +        api_key="[redacted:secret-assignment]",
      +        base_url="https://api.minimax.io/v1",
      +    )
      +
      +    headers = agent._client_kwargs.get("default_headers") or {}
      +    assert "HTTP-Referer" not in headers
      +    assert "X-Title" not in headers

1 PR closed without merging

From the close comments: merged via another change 1

  • #61347 fix(agent): reapply provider headers after model switch (#61099) · merged via another change → #61732
Closed issues · 1
  • Closed#61099[Bug]: OpenRouter logs show 'Unknown' App for Hermes Agent requests intermittently
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: 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

Merged PRs
#61732
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
10 Jul 2026

For your agent

The same facts, typed.