From 6d82b95e78391962366b6a4becc0163712fa52af Mon Sep 17 00:00:00 2001 From: Abhisek Datta Date: Mon, 15 Jun 2026 15:29:00 +0530 Subject: [PATCH] fix: use bare ::1 in NO_PROXY to avoid crashing Python httpx (#340) * fix: use bare ::1 in NO_PROXY to avoid crashing Python httpx Bracketed [::1] is URL authority syntax, not valid NO_PROXY syntax. Python's urllib/httpx parses bracketed entries as a URL and crashes with 'Invalid port: :1]'. Use the bare IPv6 loopback ::1 instead, which both Node and Python accept. Fixes #339 https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim * chore: condense NO_PROXY comment https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim * docs: note Node IPv6 literal NO_PROXY trade-off https://claude.ai/code/session_01NnkUKCn82Dc83VandSsgim --------- Co-authored-by: Claude --- internal/flows/proxy_flow.go | 7 ++++++- internal/flows/proxy_flow_env_test.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/internal/flows/proxy_flow.go b/internal/flows/proxy_flow.go index 394640b..54a2bca 100644 --- a/internal/flows/proxy_flow.go +++ b/internal/flows/proxy_flow.go @@ -425,7 +425,12 @@ func ciEnvOverride() []string { func (f *proxyFlow) setupEnvForProxy(proxyAddr, caCertPath string) []string { proxyURL := fmt.Sprintf("http://%s", proxyAddr) - noProxyList := "localhost,127.0.0.1,[::1]" + // IPv6 loopback uses the bare ::1: the bracketed [::1] is URL syntax that + // crashes Python's urllib/httpx (#339). Trade-off: Node's NODE_USE_ENV_PROXY + // (undici) only bypasses the bracketed form, so a literal http://[::1] from + // Node still gets proxied. localhost/127.0.0.1 cover the common cases; the + // IPv6 literal is a rare edge we accept since NO_PROXY can't be set per-client. + noProxyList := "localhost,127.0.0.1,::1" return []string{ "NODE_USE_ENV_PROXY=1", diff --git a/internal/flows/proxy_flow_env_test.go b/internal/flows/proxy_flow_env_test.go index bcc3e76..290cec8 100644 --- a/internal/flows/proxy_flow_env_test.go +++ b/internal/flows/proxy_flow_env_test.go @@ -41,6 +41,22 @@ func TestSetupEnvForProxyConfiguresYarn(t *testing.T) { "yarn ignores NODE_EXTRA_CA_CERTS; YARN_HTTPS_CA_FILE_PATH is required to trust the MITM CA") } +// TestSetupEnvForProxyNoProxyIPv6 proves the fix for #339: the IPv6 loopback in +// NO_PROXY must be bare (::1), not bracketed ([::1]). Brackets are URL syntax, +// not NO_PROXY syntax, and Python's urllib/httpx crashes parsing them with +// "Invalid port: ':1]'". +func TestSetupEnvForProxyNoProxyIPv6(t *testing.T) { + f := &proxyFlow{} + env := envToMap(f.setupEnvForProxy("127.0.0.1:54321", "/tmp/pmg-ca-cert.pem")) + + for _, key := range []string{"NO_PROXY", "no_proxy"} { + assert.Equal(t, "localhost,127.0.0.1,::1", env[key], + "%s must use bare ::1; bracketed [::1] is invalid NO_PROXY syntax and crashes httpx", key) + assert.NotContains(t, env[key], "[::1]", + "%s must not contain bracketed IPv6 loopback", key) + } +} + // TestCIEnvOverride proves the fix for #335: pmg forces CI=true for // non-interactive runs but must not clobber a CI value the user set // explicitly (e.g. CI=false on a build server).