Header propagation bugs across routed providers and agents

NousResearch/hermes-agent · cluster 8779 · agent

Data as of

Nothing open on GitHub. Fix merged in #50298. Check it covers your case.

The short answer

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

Multiple bugs and fixes around SDK client headers being read from wrong attributes (_default_headers vs _custom_headers vs public default_headers), causing custom or routed provider headers to be silently dropped before requests. Touches Copilot, Codex, NVIDIA NIM, Kimi, and agent transport layers.

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.

  • #50298 fix(agent): Copilot Claude chat-completions — plain httpx transport + preserve routed headers (#12066)

    merged to main

    files: agent/agent_init.py, run_agent.py, tests/run_agent/test_create_openai_client_proxy_env.py, tests/run_agent/test_provider_attribution_headers.py

    Show the diff
    • agent/agent_init.py
      @@ -808,6 +808,8 @@ def init_agent(
                       # _custom_headers; older/mocked clients may expose
                       # _default_headers instead.
                       _routed_headers = getattr(_routed_client, "_custom_headers", None)
      +                if not _routed_headers:
      +                    _routed_headers = getattr(_routed_client, "default_headers", None)
                       if not _routed_headers:
                           _routed_headers = getattr(_routed_client, "_default_headers", None)
                       if _routed_headers:
      @@ -861,6 +863,8 @@ def init_agent(
                                   if _provider_timeout is not None:
                                       client_kwargs["timeout"] = _provider_timeout
                                   _fb_headers = getattr(_fb_client, "_custom_headers", None)
      +                            if not _fb_headers:
      +                                _fb_headers = getattr(_fb_client, "default_headers", None)
                                   if not _fb_headers:
                                       _fb_headers = getattr(_fb_client, "_default_headers", None)
                                   if _fb_headers:
    • run_agent.py
      @@ -3557,6 +3557,9 @@ def _build_keepalive_http_client(base_url: str = "") -> Any:
                   import httpx as _httpx
                   import socket as _socket
       
      +            if "api.githubcopilot.com" in str(base_url or "").lower():
      +                return _httpx.Client()
      +
                   _sock_opts = [(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1)]
                   if hasattr(_socket, "TCP_KEEPIDLE"):
                       _sock_opts.append((_socket.IPPROTO_TCP, _socket.TCP_KEEPIDLE, 30))
    • tests/run_agent/test_create_openai_client_proxy_env.py
      @@ -145,6 +145,27 @@ def test_create_openai_client_no_proxy_when_env_unset(mock_openai, monkeypatch):
           http_client.close()
       
       
      +@patch("run_agent.OpenAI")
      +def test_create_openai_client_uses_plain_httpx_client_for_copilot(mock_openai, monkeypatch):
      +    """Copilot Claude chat-completions rejects the custom socket-options transport."""
      +    for key in ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY",
      +                "https_proxy", "http_proxy", "all_proxy"):
      +        monkeypatch.delenv(key, raising=False)
      +
      +    agent = _make_agent()
      +    kwargs = {
      +        "api_key": "test-key",
      +        "base_url": "https://api.githubcopilot.com",
      +    }
      +    agent._create_openai_client(kwargs, reason="test", shared=False)
      +
      +    forwarded = mock_openai.call_args.kwargs
      +    http_client = _extract_http_client(forwarded)
      +    assert isinstance(http_client, httpx.Client)
      +    assert getattr(http_client._transport._pool, "_socket_options", None) is None
      +    http_client.close()
      +
      +
       def test_get_proxy_for_base_url_returns_none_when_host_bypassed(monkeypatch):
           """NO_PROXY must suppress the proxy for matching base_urls.
       
    • tests/run_agent/test_provider_attribution_headers.py
      @@ -109,6 +109,31 @@ def test_routed_client_preserves_openai_sdk_custom_headers(mock_openai):
           assert headers["X-BILLING-INVOKE-ORIGIN"] == "HermesAgent"
       
       
      +@patch("run_agent.OpenAI")
      +def test_routed_client_preserves_openai_sdk_default_headers(mock_openai):
      +    mock_openai.return_value = MagicMock()
      +    routed_client = SimpleNamespace(
      +        api_key="[redacted:secret-assignment]",
      +        base_url="https://api.githubcopilot.com",
      +        default_headers={"copilot-integration-id": "vscode-chat"},
      +    )
      +
      +    with patch("agent.auxiliary_client.resolve_provider_client", return_value=(
      +        routed_client,
      +        "claude-opus-4.7",
      +    )):
      +        agent = AIAgent(
      +            provider="copilot",
      +            model="claude-opus-4.7",
      +            quiet_mode=True,
      +            skip_context_files=True,
      +            skip_memory=True,
      +        )
      +
      +    headers = agent._client_kwargs["default_headers"]
      +    assert headers["copilot-integration-id"] == "vscode-chat"
      +
      +
       @patch("run_agent.OpenAI")
       def test_gmi_base_url_picks_up_profile_user_agent(mock_openai):
           """GMI declares User-Agent on its ProviderProfile.default_headers.

5 PRs closed without merging

From the close comments: done elsewhere 3 · merged via another change 2

  • #8784 Bugfix: preserve routed provider default headers · done elsewhere → 3e354b6, #8799
  • #8799 fix(run_agent): use public default_headers attr when preserving routed client headers · done elsewhere
  • #15185 fix(copilot): honor SDK v2 _custom_headers + skip keepalive transport for Claude (#12066) · merged via another change → 3e354b6, #50298, #39159
  • #19985 fix(codex): copy headers from _custom_headers (SDK >= 1.x) not _default_headers · done elsewhere → 27df249
  • #39159 fix(agent): preserve copilot routed headers · merged via another change → 3e354b6, #50298, #10324
Closed issues · 3
  • Closed#8779[Bug]: run_agent.py uses _default_headers instead of default_headers, dropping routed client headers and breaking Kimi API
  • Closed#12066[Bug]: Copilot Claude chat-completions fails in Hermes with model_not_supported when direct requests succeed
  • Closed#19981[Bug]: openai-codex headers from PR #12664 are read off wrong SDK attribute (_default_headers vs _custom_headers), silently dropped before request
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.20: 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
#50298
Closed issues
3
Attempts
5 PRs closed without merging
Fix link checked
21 Jun 2026

For your agent

The same facts, typed.