fix: sanitize newlines in NO_PROXY env var before httpx client init#3519
fix: sanitize newlines in NO_PROXY env var before httpx client init#3519rkfshakti wants to merge 4 commits into
Conversation
rkfshakti
commented
Jul 20, 2026
- fix(parsing): guard against None response.output in parse_response
- fix(streaming): preserve accumulated output when response.completed has null output
- fix(streaming): route null-output fallback through parse_response
- fix(client): sanitize newlines in NO_PROXY env var before httpx client init
The chatgpt.com Codex backend sometimes sends response.output: null in the consolidated response.completed event, even when valid output_item.done events were streamed earlier. The SDK then raises TypeError: 'NoneType' object is not iterable inside the stream accumulator, killing the entire stream before the consumer can read the deltas. Fix: iterate over response.output or [] instead of response.output directly. Closes openai#3325
…as null output The chatgpt.com Codex backend sometimes sends response.output: null in the consolidated response.completed event even when valid output_item.done events were streamed earlier (issue openai#3325). The previous fix (response.output or []) prevented the TypeError but discarded the already-streamed snapshot.output, causing the final ParsedResponse to have empty output/output_text. Move the guard into ResponseStreamState.accumulate_event: when event.response.output is None and the snapshot has accumulated output items, build the completed response from the snapshot instead of calling parse_response with an empty output list. This preserves streamed text and tool calls in get_final_response() and ResponseCompletedEvent. Addresses Codex review feedback on openai#3517.
Address Codex P2 review feedback on the previous commit (1c050f6): 1. Run response parsing on the streamed fallback: instead of building ParsedResponse directly from snapshot.output via construct_type_unchecked (which left output_parsed/parsed_arguments as None), inject the streamed items into a shallow copy of the response and pass it through parse_response so text_format and parsed_arguments logic still runs. 2. Handle null completed output without streamed items: re-add the 'response.output or []' guard in parse_response() so a null-output response.completed with no prior output_item.added events returns a parsed response with an empty output list instead of raising TypeError. Together these cover both branches: null output WITH accumulated snapshot items (parse_response runs on the injected items) and null output WITHOUT items (parse_response returns empty output gracefully).
…t init httpx's get_environment_proxies() only splits NO_PROXY by comma, not by newline. When NO_PROXY contains newline characters (common in Docker environments, .env files, or shell scripts), the newline becomes part of the hostname and httpx raises InvalidURL. Add _sanitize_no_proxy() which replaces newlines with commas and strips whitespace, called from _DefaultHttpxClient.__init__() before the httpx client is constructed. Closes openai#3303.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 217dc74b35
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # only splits by comma, not by newline. When NO_PROXY contains newlines | ||
| # (common in Docker/.env files), the newline becomes part of the hostname | ||
| # and httpx raises InvalidURL. See issue #3303. | ||
| _sanitize_no_proxy() |
There was a problem hiding this comment.
Sanitize NO_PROXY before async client init
This call only runs in _DefaultHttpxClient; AsyncOpenAI creates AsyncHttpxClientWrapper from _DefaultAsyncHttpxClient (src/openai/_base_client.py:1546, 1441-1446), whose constructor still enters httpx.AsyncClient.__init__ with the original environment. In async clients with NO_PROXY/no_proxy containing newlines, the same InvalidURL during httpx environment proxy parsing remains, so the fix only works for sync users.
Useful? React with 👍 / 👎.
| type_=type(event.response), | ||
| value={ | ||
| **event.response.to_dict(), | ||
| "output": [item.to_dict() for item in snapshot.output], |
There was a problem hiding this comment.
Use output_item.done data for null-output fallback
When response.completed has output: null, this fallback copies snapshot.output, but the snapshot is populated from response.output_item.added and deltas and never replaced from response.output_item.done before this branch. For streams where the added item is still in_progress and the done event carries the completed item/final annotations, the final parsed response returned here preserves the stale in-progress item instead of the finalized output.
Useful? React with 👍 / 👎.
| becomes part of the hostname and httpx raises ``InvalidURL`` (issue #3303). | ||
| """ | ||
| for key in ("NO_PROXY", "no_proxy"): | ||
| val = os.environ.get(key) |
There was a problem hiding this comment.
Import os before using it in sanitizer
With the default sync client, _DefaultHttpxClient.__init__ now always calls _sanitize_no_proxy(), and this line references os even though _base_client.py does not import it. Any OpenAI()/DefaultHttpxClient() construction therefore raises NameError before httpx initialization, even when NO_PROXY is unset.
Useful? React with 👍 / 👎.