Files
hermes-agent/tests/hermes_cli/test_auth_codex_no_proxy_ipv6.py
brooklyn cf98cef46a fix(net): sanitize bracketed-IPv6 NO_PROXY entries so bare httpx clients construct
Clash Verge / mihomo export loopback bypasses as '[::1]' in NO_PROXY. httpx 0.28.1's
get_environment_proxies routes any bracketed entry through its wildcard branch,
producing the pattern 'all://*[::1]', and URLPattern fails to URL-parse it, so every
bare trust_env httpx.Client() raised InvalidURL('Invalid port: :1]') at construction.
Codex device login/refresh died before its first request while chat (explicit mounts)
kept working, and every hermes doctor connectivity probe misreported the crash as
provider downtime. IPv6-CIDR NO_PROXY entries ('::1/128') crash the same way.

normalize_proxy_env_vars (the in-place env seam already called when chat transports
build) now rewrites those entries to the bare IPv6 literal, which httpx compiles into
a working 'all://[<v6>]' bypass and which urllib, requests and agent.proxy_bypass all
understand — the bracketed form was matched by none of them. The bare-literal rewrite
bypasses every port, so '[::1]:8080' keeps its intent superset. _codex_http_client and
doctor's run_probes call the seam before building clients; a clean environment is
rewritten byte-stable (no-op).
2026-10-05 16:37:14 -05:00

37 lines
1.4 KiB
Python

"""``_codex_http_client`` survives a bracketed-IPv6 NO_PROXY environment (#118159).
Clash Verge / mihomo write ``[::1]`` into NO_PROXY; httpx 0.28.1 turns that into an
unparseable ``all://*[::1]`` proxy mount, so every bare ``httpx.Client()`` raised
``InvalidURL: Invalid port`` — codex device login died before its first request while chat
(explicit mounts) kept working. The client builder now sanitizes the env before construction.
"""
from __future__ import annotations
import pytest
pytest.importorskip("httpx")
# The exact environment Clash Verge / mihomo exports (issue #118159).
_CLASH_NO_PROXY = "127.0.0.1,localhost,::1,[::1]"
@pytest.fixture
def clash_env(monkeypatch):
for key in ("NO_PROXY", "no_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY",
"http_proxy", "https_proxy", "all_proxy"):
monkeypatch.delenv(key, raising=False)
monkeypatch.setenv("NO_PROXY", _CLASH_NO_PROXY)
monkeypatch.setenv("no_proxy", _CLASH_NO_PROXY)
def test_codex_http_client_constructs_under_clash_ipv6_no_proxy(clash_env):
from hermes_cli.auth import _codex_http_client
# Red on base: InvalidURL('Invalid port: :1]') is raised at httpx.Client construction.
client = _codex_http_client(timeout=5.0)
try:
assert client._mounts is not None # constructed; the bypass mount is a real entry
finally:
client.close()