fix(agent): honor configured proxy in OpenAI client path (#11609)#11733
fix(agent): honor configured proxy in OpenAI client path (#11609)#11733fangbinwei wants to merge 1 commit into
Conversation
|
LGTM. Thorough fix for #11609. The key design decision is sound: when a proxy is configured, let httpx own the entire proxy resolution (env vars, NO_PROXY, macOS SystemConfiguration, Windows registry) via Using The Tests are extensive and pin the tradeoff explicitly (keepalive on direct path, delegation on proxy path). The |
a0af18a to
5561df4
Compare
…h#11609) Injecting a custom httpx.HTTPTransport for TCP keepalives (NousResearch#10324) made httpx skip env-proxy mount construction, so HTTPS_PROXY / system proxy settings were silently ignored — requests went direct to the destination host instead of the user's proxy. Detect any configured proxy via urllib.request.getproxies() (the same function httpx's trust_env path uses internally) and skip the keepalive injection in that case, letting the SDK build a default httpx.Client with full trust_env proxy support. The proxy path intentionally drops the keepalive injection; that socket is a loopback/LAN hop to the local proxy process, and httpx's read timeout remains as a dead-peer backstop. Keepalive is still applied on the direct-connect path, so NousResearch#10324 remains addressed there. Also widen _force_close_tcp_sockets to walk both _transport and _mounts. When the proxied client path activates, live sockets live under mount pools; without this, CLOSE-WAIT cleanup silently misses them.
5561df4 to
6f2ebbb
Compare
The main OpenAI client path was injecting a custom httpx transport that bypassed proxy-aware defaults, while the raw Codex path ignored runtime-resolved base URLs and openai-codex callers could still pin chat_completions. This packages the minimal local fixes and regression tests needed to make Hermes reliably use Codex gpt-5.4 behind the user's configured proxy. Constraint: Must preserve unrelated local edits in the repo Constraint: Must keep Hermes on the Codex Responses path for openai-codex Rejected: Commit workspace-level ~/.hermes/.env changes | outside repo scope Rejected: Wait for upstream PR merges | user needs a working fork branch now Confidence: high Scope-risk: moderate Reversibility: clean Directive: If upstream merges NousResearch#11733/NousResearch#10044/NousResearch#5988, rebase this branch against their final versions before reusing it Tested: pytest tests/run_agent/test_create_openai_client_reuse.py -q Tested: pytest tests/run_agent/test_run_agent_codex_responses.py -q Tested: pytest tests/agent/test_auxiliary_client.py -q Tested: hermes chat --provider openai-codex -m gpt-5.4 -Q --max-turns 1 -q "Reply with exactly OK." Tested: hermes chat -Q --max-turns 1 -q "Reply with exactly OK." Not-tested: gateway / IM platform flows Related: NousResearch#11609 Related: NousResearch#11733 Related: NousResearch#10044 Related: NousResearch#5988
What does this PR do?
Fixes #11609 where
HTTPS_PROXY/HTTP_PROXY/ macOS SystemConfiguration proxy / Windows registry proxy were silently ignored after #11277 landed. The customhttpx.HTTPTransportinjected for TCP keepalives (#10324) made httpx skip env-proxy mount construction, so requests went direct to the destination host regardless of proxy configuration — breaking WSL users, users behind corporate proxies, and users routing outbound traffic through local proxies.This PR detects any configured proxy via
urllib.request.getproxies()— the same function httpx'strust_env=Truepath uses internally — and skips the keepalive injection in that case, letting the SDK build a defaulthttpx.Clientwith fulltrust_envproxy support, including system-level proxy configuration on macOS and Windows.The proxy path intentionally drops the keepalive injection. Re-implementing httpx's proxy resolution (env + NO_PROXY with IPv6 / CIDR / scheme-qualified entries + macOS SystemConfiguration + Windows registry) by hand proved too fragile; the httpx socket in proxy mode is a loopback/LAN hop to the local proxy process, and httpx's read timeout still fires if that side stalls. Keepalive remains applied on the direct-connect path, so #10324 stays addressed there.
Additionally widens
_force_close_tcp_socketsto walk both_transportand_mounts. Once the proxied client path activates, live sockets live under mount pools rather than the default transport, so the existing CLOSE-WAIT cleanup would otherwise silently miss them on every client rebuild.Related Issue
Fixes #11609
Type of Change
Changes Made
run_agent.py:AIAgent._has_proxy_configured()— detects configured proxy viaurllib.request.getproxies(), matching httpxtrust_envsemanticshttpx.Clientinjection onnot _has_proxy_configured()so proxied setups delegate to httpxtrust_envAIAgent._iter_client_sockets()helper walking both_transportand_mounts_force_close_tcp_socketsand_cleanup_dead_connectionsto use the new iteratortests/run_agent/test_create_openai_client_reuse.py:_has_proxy_configured/ system-proxy / mount iteration / mount force-closeurllib.request.getproxiesto insulate tests from the developer's machine configHow to Test
Scoped tests that exercise the change:
Expected: 9 passed (no failures, no skips).
End-to-end reproduction:
HTTPS_PROXY=http://127.0.0.1:<port>pointing at a local proxy, or with a macOS System Settings → Network proxy configured.lsof -i -nP | grep pythonshows outbound sockets terminating at the proxy address, not the destination host.Note:
pytest tests/ -qcurrently reports pre-existing platform/environment failures on upstreammain(Discord / Matrix / WSL / file-staleness suites); the scoped regression tests for this change pass cleanly.Checklist
Code
pytest tests/ -qand all tests passDocumentation & Housekeeping
cli-config.yaml.example— N/ACONTRIBUTING.mdorAGENTS.md— N/Agetproxies()transparently reads env vars on all platforms, macOS SystemConfiguration, and Windows registry;TCP_KEEPIDLE/TCP_KEEPALIVEplatform branch already in place