Skip to content

fix(providers): disable proxy for local endpoints, respect env proxy for cloud - #4367

Merged
Re-bin merged 2 commits into
HKUDS:mainfrom
michaelxer:fix/proxy-env-4366
Jun 17, 2026
Merged

fix(providers): disable proxy for local endpoints, respect env proxy for cloud#4367
Re-bin merged 2 commits into
HKUDS:mainfrom
michaelxer:fix/proxy-env-4366

Conversation

@michaelxer

Copy link
Copy Markdown
Contributor

Fixes #4366

Problem

When the host has HTTP_PROXY / HTTPS_PROXY / ALL_PROXY set, httpx.AsyncClient routes all traffic through the proxy — including requests to localhost or LAN addresses that the proxy typically cannot reach. This silently breaks local model servers (Ollama, llama.cpp, vLLM) behind a proxy-configured host.

Fix

Two changes in openai_compat_provider.py:

  1. Local endpoints: Create the httpx client with transport=httpx.AsyncHTTPTransport(proxy=None) to explicitly bypass proxy for local traffic. The limits parameter is also forwarded to the transport (httpx does not propagate client.limits to custom transports).

  2. Cloud endpoints: Create the httpx client with trust_env=True so corporate/VPN proxies work without explicit nanobot configuration.

How to Test

  1. Set a fake proxy: export HTTPS_PROXY=http://127.0.0.1:9999
  2. Start a local model server (e.g. Ollama on port 11434)
  3. Send a message through nanobot
  4. Before fix: connection fails (proxy cannot reach localhost)
  5. After fix: connection succeeds (proxy bypassed for local endpoints)

Automated tests:

python -m pytest tests/providers/test_proxy_env.py tests/providers/test_local_endpoint_detection.py tests/providers/test_openai_compat_timeout.py -v

Checklist

  • I searched open issues/PRs and did not find a duplicate
  • All 649 provider tests pass (pytest tests/providers/ -v)
  • New regression tests cover both local proxy-bypass and cloud trust_env paths

…for cloud

When the host has HTTP_PROXY / HTTPS_PROXY / ALL_PROXY set, httpx routes
all traffic through the proxy — including requests to localhost or LAN
addresses that the proxy typically cannot reach.  This breaks local model
servers (Ollama, llama.cpp, vLLM) silently.

- Local endpoints: pass transport=httpx.AsyncHTTPTransport(proxy=None)
  so proxy env vars are ignored for local traffic.
- Cloud endpoints: pass trust_env=True so corporate/VPN proxies work
  without explicit configuration.

Fixes HKUDS#4366

@chengyongru chengyongru left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR! The local endpoint proxy bypass is well done — using an explicit transport=httpx.AsyncHTTPTransport(proxy=None) is the correct way to skip proxy env vars, since httpx sets allow_env_proxies = trust_env and transport is None, so any non-None transport effectively disables env proxy reading.

However, the cloud branch introduces a regression.

Cloud endpoint client loses SDK defaults

Before this PR, cloud endpoints passed http_client=None to AsyncOpenAI, so the SDK created its own DefaultAsyncHttpxClient with sensible defaults:

  • follow_redirects=True
  • limits=Limits(max_connections=1000, max_keepalive_connections=100, keepalive_expiry=5.0)
  • trust_env=True (reads HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY)

The new else branch creates a bare httpx.AsyncClient(timeout=timeout_s, trust_env=True), which replaces that with:

  • follow_redirects=False (httpx default) — breaks any cloud provider that returns 3xx redirects
  • No connection pool limits — unbounded connections under concurrency
  • trust_env=True is a no-op — it's already httpx's default

Suggestion: The simplest fix is to remove the else branch entirely and let http_client stay None for cloud endpoints. The SDK's default client already reads proxy env vars through its own trust_env=True, so the stated goal (respect proxy env for cloud) is already met without any code change on that path.

if self._is_local:
    _local_limits = httpx.Limits(keepalive_expiry=0)
    http_client = httpx.AsyncClient(
        limits=_local_limits,
        timeout=timeout_s,
        transport=httpx.AsyncHTTPTransport(proxy=None, limits=_local_limits),
    )
# else: http_client stays None → SDK creates DefaultAsyncHttpxClient
# which already reads proxy env vars via trust_env=True

If there's a specific reason a custom client is needed for cloud, please carry over the SDK defaults explicitly (follow_redirects=True, proper limits).


This review was generated by nanobot — an AI agent for code review.

Comment on lines 423 to 426
http_client = httpx.AsyncClient(
timeout=timeout_s,
trust_env=True,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This custom client for cloud endpoints silently drops two SDK defaults that the pre-PR code path had:

  1. follow_redirects: httpx defaults to False, but the SDK's DefaultAsyncHttpxClient sets it to True. Some cloud providers return 3xx redirects, which would now break.

  2. Connection pool limits: the SDK sets Limits(max_connections=1000, max_keepalive_connections=100, keepalive_expiry=5.0). This bare client has no limits at all.

Also, trust_env=True here is already httpx's default, so it's redundant — the real effect of the pre-PR http_client=None path was letting the SDK create its own client with trust_env=True baked in, which already respects proxy env vars.

The simplest fix: remove this else branch and leave http_client as None for cloud endpoints.

…y defaults

chengyongru reviewed HKUDS#4367 and identified that the cloud  branch
created a bare httpx.AsyncClient that lacked the SDK's default settings
(follow_redirects, connection pool limits). Since the SDK's
DefaultAsyncHttpxClient already has trust_env=True and proper defaults,
the simplest fix is to let http_client stay None for cloud endpoints.

Also updated the test to match the new behavior (http_client is None).
@michaelxer

Copy link
Copy Markdown
Contributor Author

Good catch, thanks for the detailed review. Removed the else branch entirely — http_client stays None for cloud endpoints so the SDK creates its own DefaultAsyncHttpxClient with proper defaults (follow_redirects, connection pool limits, trust_env).

Updated the test to match (asserts http_client is None for cloud endpoints).

@chengyongru chengyongru added the bug Something isn't working label Jun 16, 2026

@chengyongru chengyongru left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix! The local-endpoint proxy bypass is correct and well-tested.

However, the cloud-endpoint branch introduces a regression: by creating a custom httpx.AsyncClient(timeout=timeout_s, trust_env=True) instead of leaving http_client=None (which lets the OpenAI SDK use DefaultAsyncHttpxClient), the cloud branch loses:

  1. follow_redirects=TrueDefaultAsyncHttpxClient sets this, httpx.AsyncClient defaults to False. Cloud providers occasionally return redirects.
  2. Connection pool limitsDefaultAsyncHttpxClient sets limits=Limits(max_connections=1000, max_keepalive_connections=100), custom client has unbounded pool.
  3. trust_env=True is redundant — httpx already defaults to True.

The fix: keep http_client=None (the original behavior) for the cloud path. The SDK's default client already respects proxy env vars via trust_env=True default. If you must create a custom client, add follow_redirects=True and connection limits to match.


Reviewed by nanobot

@chengyongru chengyongru left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The author already addressed the cloud-endpoint regression from the previous review. The else branch with the custom httpx.AsyncClient is gone — cloud endpoints now correctly keep http_client=None, letting the SDK's DefaultAsyncHttpxClient handle proxy env vars, connection pool limits, and redirects.

The local-endpoint fix is solid: transport=httpx.AsyncHTTPTransport(proxy=None) cleanly bypasses proxy env vars for localhost/LAN addresses. Tests cover both local and cloud paths.

Looks good to merge.


Reviewed by nanobot

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

Labels

bug Something isn't working valid

Projects

None yet

Development

Successfully merging this pull request may close these issues.

local model servers need setting proxy

3 participants