Skip to content

fix: always create fresh httpx.Client to prevent use-after-close (#10324 regression)#11033

Closed
surmount1 wants to merge 1 commit into
NousResearch:mainfrom
surmount1:fix/httpx-client-closed-transport-reuse
Closed

fix: always create fresh httpx.Client to prevent use-after-close (#10324 regression)#11033
surmount1 wants to merge 1 commit into
NousResearch:mainfrom
surmount1:fix/httpx-client-closed-transport-reuse

Conversation

@surmount1

Copy link
Copy Markdown

Problem

The guard introduced in #10324

if "http_client" not in client_kwargs:
    client_kwargs["http_client"] = httpx.Client(transport=HTTPTransport(...))

— caused a use-after-close bug that makes every multi-turn session fail with APIConnectionError("Connection error.") starting from the second API call.

Root Cause

_create_request_openai_client() does a shallow copy of self._client_kwargs:

request_kwargs = dict(self._client_kwargs)   # shallow copy
return self._create_openai_client(request_kwargs, reason=reason, shared=False)

Because the copy is shallow, every per-request OpenAI client receives the same httpx.Client reference as the shared/init client.

OpenAI.close() calls httpx.Client.close() internally, which marks the underlying transport as closed. After the first per-request client is closed (e.g. after a tool-call result is processed), the shared httpx.Client stored in self._client_kwargs["http_client"] is already closed. The next OpenAI client created from the same _client_kwargs inherits the closed transport and immediately raises APIConnectionError("Connection error.") without even attempting a network connection.

Observed symptom: session consistently works for the first LLM call (initial response / tool decision), then fails on every subsequent call (processing tool results, follow-up turns).

Fix

Remove the if "http_client" not in client_kwargs: guard so that _create_openai_client() always instantiates a fresh httpx.Client. Per-request clients now own their own transport; closing one does not affect any other client.

As a bonus, also forward HTTPS_PROXY / HTTP_PROXY environment variables explicitly. httpx normally reads proxy env vars automatically, but that behaviour is bypassed when a custom transport= is supplied — so users behind proxies would silently get no proxy support despite setting the standard env vars.

Verification

# Two consecutive _run_codex_stream calls — both succeed with independent httpx clients
c1 = agent._create_request_openai_client(reason="test_req_1")
r1 = agent._run_codex_stream(api_kwargs, client=c1)   # SUCCESS
agent._close_request_openai_client(c1, ...)            # closes httpx_1

c2 = agent._create_request_openai_client(reason="test_req_2")
# c2._client is a NEW httpx.Client (not the closed httpx_1)
r2 = agent._run_codex_stream(api_kwargs, client=c2)   # SUCCESS ✓

…sResearch#10324 regression)

The guard introduced in NousResearch#10324 —
  if "http_client" not in client_kwargs:
— caused a use-after-close bug in multi-turn sessions.

Root cause:
_create_request_openai_client() does a *shallow* copy of
self._client_kwargs, so every per-request OpenAI client receives the
same httpx.Client reference as the shared/init client.

OpenAI.close() calls httpx.Client.close() internally, which marks the
transport as closed.  The first per-request client's cleanup therefore
closes the shared transport, and every subsequent OpenAI client built
from the same self._client_kwargs inherits a closed transport and
immediately raises APIConnectionError("Connection error.") — typically
observable on the 2nd API call within a session (e.g. after a tool
result is returned).

Fix:
Remove the guard so _create_openai_client() always instantiates a
fresh httpx.Client.  Per-request clients now own their own transport;
closing one does not affect any other client.

Additionally, honour HTTPS_PROXY / HTTP_PROXY environment variables.
httpx normally reads these automatically, but that behaviour is
bypassed when a custom transport is passed.  We forward the env vars
explicitly so proxy settings work as expected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

The httpx keepalive code this patches was already reverted on main — _create_openai_client no longer injects a custom http_client into client_kwargs. The use-after-close path doesn't exist on current main. Thanks for investigating the regression!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants