Codex backend extra_headers handling fixes

NousResearch/hermes-agent · cluster 26599 · agent

Data as of

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

The short answer

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

These issues and PRs address problems with extra_headers being incorrectly sent to the Codex/chatgpt.com backend, causing HTTP 400 errors, alongside related work on session and cache-routing headers.

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.

  • #45633 fix(codex): drop extra_headers for chatgpt.com backend

    merged to main

    files: agent/transports/codex.py, tests/agent/transports/test_codex_transport.py

  • #47836 fix(codex): restore session_id/x-client-request-id HTTP headers for cache routing (#47335)

    merged to main

    files: agent/transports/codex.py, tests/agent/transports/test_codex_transport.py

    Show the diff
    • agent/transports/codex.py
      @@ -218,10 +218,28 @@ def build_kwargs(
                   kwargs.pop("timeout", None)
       
               if is_codex_backend:
      -            # chatgpt.com/backend-api/codex rejects body-level
      -            # ``extra_headers`` with HTTP 400. Correlation/cache routing for
      -            # this backend must not be sent through the Responses payload.
      -            kwargs.pop("extra_headers", None)
      +            # The Codex backend rejects body-level ``extra_headers`` with
      +            # HTTP 400, but the OpenAI SDK's ``extra_headers`` kwarg maps
      +            # to actual HTTP request headers (not body fields).  We need
      +            # these headers for cache-scope routing so prompt cache hits
      +            # remain high.  Send session_id / x-client-request-id as HTTP
      +            # headers while keeping ``prompt_cache_key`` in the body for
      +            # standard OpenAI routing as a belt-and-braces fallback.
      +            cache_scope_id = str(session_id or "").strip()
      +            if cache_scope_id:
      +                existing_extra_headers = kwargs.get("extra_headers")
      +                merged_extra_headers: Dict[str, str] = {}
      +                if isinstance(existing_extra_headers, dict):
      +                    merged_extra_headers.update(
      +                        {
      +                            str(key): str(value)
      +                            for key, value in existing_extra_headers.items()
      +                            if key and value is not None
      +                        }
      +                    )
      +                merged_extra_headers["session_id"] = cache_scope_id
      +                merged_extra_headers["x-client-request-id"] = cache_scope_id
      +                kwargs["extra_headers"] = merged_extra_headers
       
               max_tokens = [redacted:secret-assignment]"max_tokens")
               if max_tokens is not None and not is_codex_backend:
    • tests/agent/transports/test_codex_transport.py
      @@ -155,7 +155,9 @@ def test_codex_backend_no_max_output_tokens(self, transport):
               )
               assert "max_output_tokens" not in kw
       
      -    def test_codex_backend_does_not_set_extra_headers(self, transport):
      +    def test_codex_backend_sets_cache_routing_headers(self, transport):
      +        """Codex backend sends session_id / x-client-request-id as HTTP
      +        headers (via extra_headers) for cache-scope routing."""
               messages = [{"role": "user", "content": "Hi"}]
       
               kw = transport.build_kwargs(
      @@ -166,9 +168,23 @@ def test_codex_backend_does_not_set_extra_headers(self, transport):
                   is_codex_backend=True,
               )
       
      +        headers = kw.get("extra_headers", {})
      +        assert headers.get("session_id") == "conv-codex-1"
      +        assert headers.get("x-client-request-id") == "conv-codex-1"
      +
      +    def test_codex_backend_no_headers_without_session_id(self, transport):
      +        messages = [{"role": "user", "content": "Hi"}]
      +
      +        kw = transport.build_kwargs(
      +            model="gpt-5.4",
      +            messages=messages,
      +            tools=[],
      +            is_codex_backend=True,
      +        )
      +
               assert "extra_headers" not in kw
       
      -    def test_codex_backend_strips_caller_extra_headers(self, transport):
      +    def test_codex_backend_preserves_caller_extra_headers(self, transport):
               messages = [{"role": "user", "content": "Hi"}]
       
               kw = transport.build_kwargs(
      @@ -180,7 +196,10 @@ def test_codex_backend_strips_caller_extra_headers(self, transport):
                   request_overrides={"extra_headers": {"x-test": "1"}},
               )
       
      -        assert "extra_headers" not in kw
      +        headers = kw.get("extra_headers", {})
      +        assert headers.get("x-test") == "1"
      +        assert headers.get("session_id") == "conv-codex-1"
      +        assert headers.get("x-client-request-id") == "conv-codex-1"
       
           def test_non_codex_responses_preserves_caller_extra_headers(self, transport):
               messages = [{"role": "user", "content": "Hi"}]

2 PRs closed without merging

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

  • #26749 fix(codex): drop extra_headers injection for chatgpt.com Codex backend (#26599) · merged via another change → 4fd9397, #45633
  • #26767 fix(codex): avoid extra_headers on Codex backend · done elsewhere → #45633, #26749, #26599
Closed issues · 1
  • Closed#26599[Bug]: Codex backend rejects 'extra_headers' field in Responses payload (HTTP 400)
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.40: 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
#45633, #47836
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
17 Jun 2026

For your agent

The same facts, typed.