From 81e79f62c64a40a0be983bfdc2ffc8eb843d8bd8 Mon Sep 17 00:00:00 2001 From: z Date: Sat, 9 May 2026 00:48:20 +0800 Subject: [PATCH] fix(sync-httpx): respect proxy env vars when force_ipv4 is enabled When `force_ipv4: true` is set in config, the sync HTTPHandler creates a custom `HTTPTransport(local_address="0.0.0.0")` which causes httpx to ignore `HTTP_PROXY`/`HTTPS_PROXY` environment variables. This breaks providers that use the sync client path (e.g. ChatGPT authenticator) when running behind a proxy. The fix detects proxy env vars at client creation time and skips the custom transport so httpx auto-detects proxy settings. When no proxy is configured, the IPv4-forcing transport is used as before. Closes #24562 See also #24983 --- litellm/llms/custom_httpx/http_handler.py | 13 +++++ .../llms/custom_httpx/test_http_handler.py | 58 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index dd955c23d23..a31761e7062 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -1047,6 +1047,19 @@ class HTTPHandler: if client is None: transport = self._create_sync_transport() + # When proxy is configured via env vars, skip custom transport. + # httpx ignores proxy env vars when an explicit transport is + # provided, which breaks providers that use the sync client + # (e.g. ChatGPT authenticator) behind a proxy. + proxy_url = ( + os.environ.get("HTTPS_PROXY") + or os.environ.get("https_proxy") + or os.environ.get("HTTP_PROXY") + or os.environ.get("http_proxy") + ) + if proxy_url and transport is not None: + transport = None + # Create a client with a connection pool self.client = httpx.Client( transport=transport, diff --git a/tests/test_litellm/llms/custom_httpx/test_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_http_handler.py index dd52304a703..809627d3e7e 100644 --- a/tests/test_litellm/llms/custom_httpx/test_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_http_handler.py @@ -675,6 +675,64 @@ async def test_httpx_handler_uses_env_user_agent(monkeypatch): await handler.close() +def test_sync_http_handler_skips_custom_transport_when_proxy_set(monkeypatch): + """ + When HTTPS_PROXY is set and force_ipv4 creates a custom transport, + the sync HTTPHandler must skip the custom transport so that httpx + auto-detects proxy from environment variables. + + httpx ignores proxy env vars when an explicit transport is provided, + which breaks sync-path providers (e.g. ChatGPT authenticator) behind + a proxy. See GitHub issue #24562. + """ + original_force_ipv4 = litellm.force_ipv4 + litellm.force_ipv4 = True + + try: + # With proxy: custom transport should be dropped + monkeypatch.setenv("HTTPS_PROXY", "http://proxy:8080") + handler = HTTPHandler() + try: + transport = handler.client._transport + assert not isinstance(transport, httpx.HTTPTransport) + finally: + handler.close() + + # Without proxy: custom transport should be applied (force_ipv4) + monkeypatch.delenv("HTTPS_PROXY", raising=False) + monkeypatch.delenv("HTTP_PROXY", raising=False) + handler2 = HTTPHandler() + try: + transport2 = handler2.client._transport + assert isinstance(transport2, httpx.HTTPTransport) + finally: + handler2.close() + finally: + litellm.force_ipv4 = original_force_ipv4 + + +def test_sync_http_handler_prefers_https_proxy_over_http_proxy(monkeypatch): + """ + HTTPS_PROXY takes precedence over HTTP_PROXY, matching httpx convention. + """ + original_force_ipv4 = litellm.force_ipv4 + litellm.force_ipv4 = True + + try: + monkeypatch.setenv("HTTPS_PROXY", "http://https-proxy:8080") + monkeypatch.setenv("HTTP_PROXY", "http://http-proxy:8080") + + handler = HTTPHandler() + try: + # Should not have custom transport (proxy takes priority) + transport = handler.client._transport + assert not isinstance(transport, httpx.HTTPTransport) + finally: + handler.close() + finally: + litellm.force_ipv4 = original_force_ipv4 + + def test_get_httpx_client_applies_float_timeout_without_mocking_handler(): """ Exercise real _get_httpx_client + HTTPHandler: params={'timeout': x} must reach httpx.Client(timeout=...).