hermes - ✅(Solved) Fix Bug: _build_keepalive_http_client ignores no_proxy — localhost requests go through proxy [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14451Fetched 2026-04-24 06:17:12
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Author
Timeline (top)
labeled ×4commented ×1cross-referenced ×1referenced ×1

Root Cause

In run_agent.py:4418-4424, a custom httpx.HTTPTransport is created for TCP keepalive. Because httpx disables automatic proxy env detection when a custom transport is provided, the code explicitly reads proxy settings via _get_proxy_from_env().

However, _get_proxy_from_env() (run_agent.py:180-191) only reads proxy URLs (HTTP_PROXY, HTTPS_PROXY, etc.) and never checks no_proxy. The returned proxy URL is then passed directly as proxy=_proxy to httpx.Client(...), which applies it unconditionally to all requests.

# run_agent.py:4418-4425
_proxy = _get_proxy_from_env()          # ← reads http_proxy but ignores no_proxy
return _httpx.Client(
    transport=_httpx.HTTPTransport(socket_options=_sock_opts),
    proxy=_proxy,                        # ← applied to ALL requests including localhost
)
# run_agent.py:180-191
def _get_proxy_from_env() -> Optional[str]:
    for key in ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY",
                "https_proxy", "http_proxy", "all_proxy"):
        value = os.environ.get(key, "").strip()
        if value:
            return normalize_proxy_url(value)
    return None
    # ← no_proxy / NO_PROXY is never checked

In contrast, when httpx auto-detects proxies (trust_env=True without custom transport), it correctly parses no_proxy and builds mount-based routing that skips the proxy for matching hosts.

Fix Action

Workaround

Unset proxy env vars before running Hermes:

alias hermes='http_proxy="" https_proxy="" command hermes'

Then configure proxy explicitly in the LiteLLM startup script, so LiteLLM can still reach external APIs.

PR fix notes

PR #14790: fix(agent): respect no_proxy in _build_keepalive_http_client

Description (problem / solution / changelog)

What does this PR do?

_get_proxy_from_env() reads HTTP(S)_PROXY but never checks no_proxy/NO_PROXY. The custom httpx.Client in _build_keepalive_http_client() applies the proxy unconditionally to all requests, including localhost and LAN endpoints that should bypass it. This causes connection-refused or tunnel errors when users run local models (Ollama, LM Studio) behind a corporate proxy.

Related Issue

Fixes #14451

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • run_agent.py: Added no_proxy check in _get_proxy_from_env()
  • tests/run_agent/test_create_openai_client_proxy_env.py: 15 tests

How to Test

python -m pytest -o 'addopts=' tests/run_agent/test_create_openai_client_proxy_env.py -v

Result: 15 passed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 15 (Darwin 24.6.0), Python 3.14.2

Documentation & Housekeeping

  • N/A for all documentation items

Changed files

  • run_agent.py (modified, +45/-3)
  • tests/run_agent/test_create_openai_client_proxy_env.py (modified, +136/-1)

Code Example

Hermes  →  localhost:4000 (LiteLLM Proxy)AWS Bedrock

---

export http_proxy="http://corporate-proxy:8118"    # for external traffic
export no_proxy="localhost,127.0.0.1"               # local traffic should bypass proxy

---

# run_agent.py:4418-4425
_proxy = _get_proxy_from_env()          # ← reads http_proxy but ignores no_proxy
return _httpx.Client(
    transport=_httpx.HTTPTransport(socket_options=_sock_opts),
    proxy=_proxy,                        # ← applied to ALL requests including localhost
)

---

# run_agent.py:180-191
def _get_proxy_from_env() -> Optional[str]:
    for key in ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY",
                "https_proxy", "http_proxy", "all_proxy"):
        value = os.environ.get(key, "").strip()
        if value:
            return normalize_proxy_url(value)
    return None
    # ← no_proxy / NO_PROXY is never checked

---

export http_proxy="http://corporate-proxy.example.com:8118"
   export no_proxy="localhost,127.0.0.1"

---

alias hermes='http_proxy="" https_proxy="" command hermes'
RAW_BUFFERClick to expand / collapse

This issue was filed by Claude Opus 4.6 (via Claude Code) on behalf of the user.

Bug Description

_build_keepalive_http_client() in run_agent.py ignores the no_proxy / NO_PROXY environment variable when constructing the custom httpx.Client. This causes all HTTP requests — including those to localhost / 127.0.0.1 — to be routed through the proxy, even when the user has explicitly excluded them via no_proxy.

Real-World Scenario

A common setup for using Hermes with AWS Bedrock via a local LiteLLM proxy:

Hermes  →  localhost:4000 (LiteLLM Proxy)  →  AWS Bedrock

In a corporate network, outbound traffic goes through a forward proxy (e.g., Squid). The environment is configured as:

export http_proxy="http://corporate-proxy:8118"    # for external traffic
export no_proxy="localhost,127.0.0.1"               # local traffic should bypass proxy

What should happen: Hermes sends requests directly to localhost:4000 (LiteLLM), and LiteLLM uses the proxy to reach AWS Bedrock. The no_proxy setting ensures local traffic stays local.

What actually happens: Hermes ignores no_proxy and sends the localhost:4000 request through the corporate proxy. The proxy rejects it with ERR_ACCESS_DENIED (HTTP 403), because it refuses to forward requests to internal addresses.

As a result, Hermes cannot communicate with the local LiteLLM proxy at all, even though LiteLLM itself is running correctly (verified by direct curl to localhost:4000).

Root Cause

In run_agent.py:4418-4424, a custom httpx.HTTPTransport is created for TCP keepalive. Because httpx disables automatic proxy env detection when a custom transport is provided, the code explicitly reads proxy settings via _get_proxy_from_env().

However, _get_proxy_from_env() (run_agent.py:180-191) only reads proxy URLs (HTTP_PROXY, HTTPS_PROXY, etc.) and never checks no_proxy. The returned proxy URL is then passed directly as proxy=_proxy to httpx.Client(...), which applies it unconditionally to all requests.

# run_agent.py:4418-4425
_proxy = _get_proxy_from_env()          # ← reads http_proxy but ignores no_proxy
return _httpx.Client(
    transport=_httpx.HTTPTransport(socket_options=_sock_opts),
    proxy=_proxy,                        # ← applied to ALL requests including localhost
)
# run_agent.py:180-191
def _get_proxy_from_env() -> Optional[str]:
    for key in ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY",
                "https_proxy", "http_proxy", "all_proxy"):
        value = os.environ.get(key, "").strip()
        if value:
            return normalize_proxy_url(value)
    return None
    # ← no_proxy / NO_PROXY is never checked

In contrast, when httpx auto-detects proxies (trust_env=True without custom transport), it correctly parses no_proxy and builds mount-based routing that skips the proxy for matching hosts.

Steps to Reproduce

  1. Set environment variables:

    export http_proxy="http://corporate-proxy.example.com:8118"
    export no_proxy="localhost,127.0.0.1"
  2. Run a local LiteLLM proxy on localhost:4000 (e.g., bridging to AWS Bedrock)

  3. Configure Hermes to use custom:litellm provider with base_url: http://localhost:4000/v1

  4. Run hermes chat -q "hello"

Expected: Request goes directly to localhost:4000 (bypassing proxy per no_proxy)

Actual: Request is sent through the corporate proxy, which returns HTTP 403 (ERR_ACCESS_DENIED)

Verification

  • curl http://localhost:4000/v1/chat/completions ... works perfectly (curl respects no_proxy)
  • python3 -c "import openai; client = openai.OpenAI(base_url='http://localhost:4000/v1', ...); ..." also works (default httpx respects no_proxy)
  • Only Hermes fails, because its custom _build_keepalive_http_client() bypasses no_proxy

Workaround

Unset proxy env vars before running Hermes:

alias hermes='http_proxy="" https_proxy="" command hermes'

Then configure proxy explicitly in the LiteLLM startup script, so LiteLLM can still reach external APIs.

Suggested Fix

Option A: Parse no_proxy in _build_keepalive_http_client and use httpx's mount-based routing to exclude matching hosts.

Option B: Instead of passing proxy= directly, replicate httpx's trust_env behavior by building proper proxy mounts that respect no_proxy.

Environment

  • Hermes Agent v0.10.0 (commit 3e652f75)
  • Python 3.11.8
  • httpx 0.28.1
  • OpenAI SDK 2.24.0
  • macOS Darwin 24.6.0

🤖 This issue was authored by Claude Opus 4.6 (Anthropic) via Claude Code, with human review and approval.

extent analysis

TL;DR

The issue can be fixed by modifying the _build_keepalive_http_client function to respect the no_proxy environment variable when constructing the custom httpx.Client.

Guidance

  • The root cause of the issue is that the _get_proxy_from_env function only reads proxy URLs and never checks the no_proxy environment variable.
  • To fix this, the _build_keepalive_http_client function should be modified to parse the no_proxy environment variable and use httpx's mount-based routing to exclude matching hosts.
  • The httpx library's trust_env parameter can be used to automatically detect and respect the no_proxy environment variable.
  • As a temporary workaround, the proxy environment variables can be unset before running Hermes, and the proxy can be configured explicitly in the LiteLLM startup script.

Example

import os
import httpx

# ...

def _build_keepalive_http_client():
    # ...
    no_proxy = os.environ.get('no_proxy', '').split(',')
    proxy = _get_proxy_from_env()
    if proxy:
        # Use httpx's mount-based routing to exclude matching hosts
        mounts = {}
        for host in no_proxy:
            mounts[f'http://{host}'] = httpx.HTTPTransport()
            mounts[f'https://{host}'] = httpx.HTTPTransport()
        return httpx.Client(
            transport=httpx.HTTPTransport(),
            mounts=mounts,
            proxy=proxy
        )
    else:
        return httpx.Client(
            transport=httpx.HTTPTransport()
        )

Notes

  • The provided example code snippet is a possible solution, but it may need to be adapted to the specific requirements of the Hermes Agent.
  • The httpx library's trust_env parameter can be used to automatically detect and respect the no_proxy environment variable, but this may not be compatible with the custom httpx.HTTPTransport

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING