Skip to content

fix(agent): respect proxy env vars when transport is injected - #11666

Closed
a252937166 wants to merge 1 commit into
NousResearch:mainfrom
a252937166:fix/keepalive-transport-proxy-bypass
Closed

fix(agent): respect proxy env vars when transport is injected#11666
a252937166 wants to merge 1 commit into
NousResearch:mainfrom
a252937166:fix/keepalive-transport-proxy-bypass

Conversation

@a252937166

@a252937166 a252937166 commented Apr 17, 2026

Copy link
Copy Markdown

Summary

Fix a silent proxy bypass in _create_openai_client. The custom HTTPTransport used for TCP keepalives disables httpx's env-proxy autoconfig — every LLM provider call becomes a direct connection even when HTTPS_PROXY / HTTP_PROXY / ALL_PROXY is set.

Root cause

httpx.Client only reads proxy env vars when it constructs its own transport internally. Once transport=httpx.HTTPTransport(...) is passed explicitly — as the keepalive injection does — proxy resolution becomes the caller's responsibility. Nothing in _create_openai_client was reading the env, so proxies were silently dropped for every provider call.

Impact

Users behind a mandatory proxy (corporate network, restricted egress, etc.) see:

  • APIConnectionError: Connection error.
  • APITimeoutError: Request timed out.

…while curl and stock httpx work in the same shell, and ps eww $HERMES_PID shows HTTPS_PROXY is present in the agent's process env. Hard to diagnose because every surface-level check passes.

Reproduction

import os, httpx, socket
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:PORT"

# httpx default — reads env, request goes through proxy
httpx.Client().get("https://chatgpt.com")

# hermes-style — custom transport, env ignored
sock_opts = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]
httpx.Client(transport=httpx.HTTPTransport(
    socket_options=sock_opts,
)).get("https://chatgpt.com")

The second call bypasses the proxy entirely and fails on any network where direct egress to the endpoint is blocked.

Fix

Read the standard proxy env vars (HTTPS_PROXY > HTTP_PROXY > ALL_PROXY, both upper and lower case) and pass them to HTTPTransport(proxy=...). When no env var is set, proxy=None matches the httpx default — zero behavior change for users without a proxy.

SOCKS URLs are skipped. httpx.HTTPTransport does not accept socks5:// / socks5h:// / socks4:// URLs without the httpx-socks extra, and raising at client construction would regress users whose shell exports ALL_PROXY=socks5://... (common on macOS with Clash / ShadowsocksX, where the HTTP proxy env var is already the one that should be picked up). We filter SOCKS URLs out so the keepalive injection still succeeds and the HTTP proxy env var (if any) wins.

Test plan

  • Reproduced on macOS 15.4.1 / Python 3.11 / httpx 0.28.1 with a local HTTP proxy: HTTPS_PROXY set in shell + ~/.hermes/.env, hermes ignored it, chatgpt.com codex endpoint errored out with APIConnectionError / APITimeoutError
  • Applied patch: same env, hermes connects through proxy successfully
  • proxy=None path (no env vars): behavior unchanged — HTTPTransport(proxy=None) is a no-op relative to HTTPTransport()
  • SOCKS filter checked against socks5://…, socks5h://…, socks4://…, SOCKS5://… (all → None) and http://…, https://…, "", None (all passed through unchanged)
  • Ran pytest tests/run_agent/test_create_openai_client_reuse.py -v against the patched file — both pre-existing tests pass:
    • test_second_create_does_not_wrap_closed_transport_from_first
    • test_replace_primary_openai_client_survives_repeated_rebuilds

Platforms tested

  • macOS 15.4.1 (Darwin 24.4.0), Python 3.11.8, httpx 0.28.1, local HTTP proxy

🤖 Generated with Claude Code

httpx.Client(transport=...) disables httpx's env-proxy autoconfig — proxies
are a transport-layer concern and only apply when httpx constructs its own
transport. _create_openai_client() injects a custom HTTPTransport to enable
TCP keepalives, which silently bypasses HTTPS_PROXY / HTTP_PROXY / ALL_PROXY
for every provider call. Users behind a mandatory proxy see
APIConnectionError / APITimeoutError with no proxy actually in use, while
curl and stock httpx work in the same shell.

Read the standard proxy env vars and pass them to HTTPTransport(proxy=...)
explicitly, preserving the keepalive socket options. When no proxy is set,
proxy=None matches the httpx default — zero behavior change otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@a252937166
a252937166 force-pushed the fix/keepalive-transport-proxy-bypass branch from fa9c206 to e2776f2 Compare April 17, 2026 15:37
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/config Config system, migrations, profiles labels Apr 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #12657 (merged, same proxy bypass fix) and #12010 (open, alternative approach skipping keepalive transport when proxy configured). Check if #12657 already resolved this.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the detailed write-up and for tracking down the httpx proxy bypass! This is a real bug and your diagnosis is correct.

However, the same fix was already landed on main via PR #12657 (merged 2026-04-19):

  • run_agent.py:172_get_proxy_from_env() reads HTTPS_PROXYHTTP_PROXYALL_PROXY (both cases)
  • run_agent.py:186_get_proxy_for_base_url() also honors NO_PROXY
  • run_agent.py:4833–4836_build_keepalive_http_client forwards the proxy to httpx.Client(proxy=_proxy, transport=HTTPTransport(...)) exactly as proposed here
  • Merge commit: 023208b17 (cherry-picked from contributor @heykb's work in fix(agent): respect HTTP_PROXY/HTTPS_PROXY when using custom httpx transport #12540)

This is an automated hermes-sweeper review. Closing as already implemented on main.

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

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants