{"repo":"NousResearch/hermes-agent","public_id":"8779","display_label":"Header propagation bugs across routed providers and agents","summary":"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.","subsystem":"agent","counts":{"members":9,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":1,"attempt_prs":5},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":50298},"resolution":{"state":"partially_resolved","by":"pr-50298"},"freshness":{"state":"verified","last_verified_at":"2026-06-21T18:29:49+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.19666666666666666},"fix_edges":[{"issue_number":12066,"pr_number":50298,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":8779,"title":"[Bug]: run_agent.py uses _default_headers instead of default_headers, dropping routed client headers and breaking Kimi API","url":null,"state":"closed"},{"kind":"issue","number":12066,"title":"[Bug]: Copilot Claude chat-completions fails in Hermes with model_not_supported when direct requests succeed","url":null,"state":"closed"},{"kind":"issue","number":19981,"title":"[Bug]: openai-codex headers from PR #12664 are read off wrong SDK attribute (_default_headers vs _custom_headers), silently dropped before request","url":null,"state":"closed"},{"kind":"pr","number":8784,"title":"Bugfix: preserve routed provider default headers","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3e354b61dbe7ae0870efcf0158bb0bb3c9538eeb","number":0},{"kind":"pr","value":"#8799","number":8799}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":8799,"title":"fix(run_agent): use public default_headers attr when preserving routed client headers","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":15185,"title":"fix(copilot): honor SDK v2 _custom_headers + skip keepalive transport for Claude (#12066)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3e354b61d","number":0},{"kind":"pr","value":"#50298","number":50298},{"kind":"pr","value":"#39159","number":39159}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":19985,"title":"fix(codex): copy headers from _custom_headers (SDK >= 1.x) not _default_headers","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"27df249564b3ce6fa4d1db883df0329bfda01593","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":39159,"title":"fix(agent): preserve copilot routed headers","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3e354b61d","number":0},{"kind":"pr","value":"#50298","number":50298},{"kind":"pr","value":"#10324","number":10324}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":50298,"title":"fix(agent): Copilot Claude chat-completions — plain httpx transport + preserve routed headers (#12066)","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","run_agent.py","tests/run_agent/test_create_openai_client_proxy_env.py","tests/run_agent/test_provider_attribution_headers.py"],"fix_hunks":[{"file":"agent/agent_init.py","hunks":["@@ -808,6 +808,8 @@ def init_agent(\n                 # _custom_headers; older/mocked clients may expose\n                 # _default_headers instead.\n                 _routed_headers = getattr(_routed_client, \"_custom_headers\", None)\n+                if not _routed_headers:\n+                    _routed_headers = getattr(_routed_client, \"default_headers\", None)\n                 if not _routed_headers:\n                     _routed_headers = getattr(_routed_client, \"_default_headers\", None)\n                 if _routed_headers:","@@ -861,6 +863,8 @@ def init_agent(\n                             if _provider_timeout is not None:\n                                 client_kwargs[\"timeout\"] = _provider_timeout\n                             _fb_headers = getattr(_fb_client, \"_custom_headers\", None)\n+                            if not _fb_headers:\n+                                _fb_headers = getattr(_fb_client, \"default_headers\", None)\n                             if not _fb_headers:\n                                 _fb_headers = getattr(_fb_client, \"_default_headers\", None)\n                             if _fb_headers:"]},{"file":"run_agent.py","hunks":["@@ -3557,6 +3557,9 @@ def _build_keepalive_http_client(base_url: str = \"\") -> Any:\n             import httpx as _httpx\n             import socket as _socket\n \n+            if \"api.githubcopilot.com\" in str(base_url or \"\").lower():\n+                return _httpx.Client()\n+\n             _sock_opts = [(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1)]\n             if hasattr(_socket, \"TCP_KEEPIDLE\"):\n                 _sock_opts.append((_socket.IPPROTO_TCP, _socket.TCP_KEEPIDLE, 30))"]},{"file":"tests/run_agent/test_create_openai_client_proxy_env.py","hunks":["@@ -145,6 +145,27 @@ def test_create_openai_client_no_proxy_when_env_unset(mock_openai, monkeypatch):\n     http_client.close()\n \n \n+@patch(\"run_agent.OpenAI\")\n+def test_create_openai_client_uses_plain_httpx_client_for_copilot(mock_openai, monkeypatch):\n+    \"\"\"Copilot Claude chat-completions rejects the custom socket-options transport.\"\"\"\n+    for key in (\"HTTPS_PROXY\", \"HTTP_PROXY\", \"ALL_PROXY\",\n+                \"https_proxy\", \"http_proxy\", \"all_proxy\"):\n+        monkeypatch.delenv(key, raising=False)\n+\n+    agent = _make_agent()\n+    kwargs = {\n+        \"api_key\": \"test-key\",\n+        \"base_url\": \"https://api.githubcopilot.com\",\n+    }\n+    agent._create_openai_client(kwargs, reason=\"test\", shared=False)\n+\n+    forwarded = mock_openai.call_args.kwargs\n+    http_client = _extract_http_client(forwarded)\n+    assert isinstance(http_client, httpx.Client)\n+    assert getattr(http_client._transport._pool, \"_socket_options\", None) is None\n+    http_client.close()\n+\n+\n def test_get_proxy_for_base_url_returns_none_when_host_bypassed(monkeypatch):\n     \"\"\"NO_PROXY must suppress the proxy for matching base_urls.\n "]},{"file":"tests/run_agent/test_provider_attribution_headers.py","hunks":["@@ -109,6 +109,31 @@ def test_routed_client_preserves_openai_sdk_custom_headers(mock_openai):\n     assert headers[\"X-BILLING-INVOKE-ORIGIN\"] == \"HermesAgent\"\n \n \n+@patch(\"run_agent.OpenAI\")\n+def test_routed_client_preserves_openai_sdk_default_headers(mock_openai):\n+    mock_openai.return_value = MagicMock()\n+    routed_client = SimpleNamespace(\n+        api_key=\"[redacted:secret-assignment]\",\n+        base_url=\"https://api.githubcopilot.com\",\n+        default_headers={\"copilot-integration-id\": \"vscode-chat\"},\n+    )\n+\n+    with patch(\"agent.auxiliary_client.resolve_provider_client\", return_value=(\n+        routed_client,\n+        \"claude-opus-4.7\",\n+    )):\n+        agent = AIAgent(\n+            provider=\"copilot\",\n+            model=\"claude-opus-4.7\",\n+            quiet_mode=True,\n+            skip_context_files=True,\n+            skip_memory=True,\n+        )\n+\n+    headers = agent._client_kwargs[\"default_headers\"]\n+    assert headers[\"copilot-integration-id\"] == \"vscode-chat\"\n+\n+\n @patch(\"run_agent.OpenAI\")\n def test_gmi_base_url_picks_up_profile_user_agent(mock_openai):\n     \"\"\"GMI declares User-Agent on its ProviderProfile.default_headers.\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}