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).
37 lines
1.4 KiB
Python
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()
|