Product or interface
Source build or repository tooling
(The defect is in CLI startup code: packages/tui/src/cli/main.ts:97 calls configureTuiNetworkProxy, so all CLI modes share it. I verified it only against the current source at 33b259b, not against a released package, so I am not claiming published-build behavior.)
Version
Source commit 33b259b, Node.js v24.21.0, pnpm 9.12.0
Platform
macOS
OS version and architecture
macOS 15 arm64 (Darwin 25.5.0)
Issue area
Other (proxy / network routing configuration at CLI startup)
Steps to reproduce
resolveTuiProxyConfiguration and shouldBypassTuiProxy are both exported, so this runs against the repository file in place. Nothing is copied or modified.
After pnpm install --frozen-lockfile:
import { resolveTuiProxyConfiguration, shouldBypassTuiProxy }
from './packages/tui/src/cli/network-proxy.ts';
const P = 'http://proxy.corp:8080';
const cases = [
['A no_proxy only (control)', { https_proxy: P, no_proxy: 'internal.corp' }],
['B NO_PROXY="" + no_proxy set', { https_proxy: P, NO_PROXY: '', no_proxy: 'internal.corp' }],
['C NO_PROXY=" " + no_proxy set', { https_proxy: P, NO_PROXY: ' ', no_proxy: 'internal.corp' }],
['D HTTPS_PROXY="" + https_proxy', { HTTPS_PROXY: '', https_proxy: P }],
];
for (const [label, env] of cases) {
const c = resolveTuiProxyConfiguration(env);
console.log(label, c.noProxy,
shouldBypassTuiProxy(new URL('https://internal.corp/v1'), c.noProxy));
}
Run with node repro.mjs.
Expected and actual behavior
Expected: no_proxy=internal.corp is honored, so https://internal.corp/v1 bypasses the proxy, the same way an empty uppercase HTTPS_PROXY falls through to lowercase https_proxy (case D).
Actual: when NO_PROXY is set but empty or whitespace-only, the lowercase no_proxy is discarded and the host is routed through the proxy.
resolveTuiProxyConfiguration reads four proxy variables. Three go through firstProxyValue, which trims, treats empty as unset, and falls back across spellings. NO_PROXY is the only one that does not:
// packages/tui/src/cli/network-proxy.ts:34-37
const allProxy = firstProxyValue(environment, 'ALL_PROXY', 'all_proxy');
const httpProxy = firstProxyValue(environment, 'HTTP_PROXY', 'http_proxy') ?? allProxy;
const httpsProxy = firstProxyValue(environment, 'HTTPS_PROXY', 'https_proxy') ?? allProxy ?? httpProxy;
// packages/tui/src/cli/network-proxy.ts:47
noProxy: withLoopbackNoProxy(environment.NO_PROXY ?? environment.no_proxy),
?? falls through only on null/undefined, so an empty NO_PROXY is treated as an authoritative empty bypass list. There is also no .trim(), so NO_PROXY=" " behaves the same way.
Why this looks unintended rather than deliberate: an empty HTTPS_PROXY does fall through to lowercase https_proxy, because firstProxyValue treats empty as "not set". Only NO_PROXY gives an empty value authority. Within a single function the same environment shape is interpreted two different ways.
The file has not changed since the initial source import (git log --oneline -- packages/tui/src/cli/network-proxy.ts → c59cf53 only), so the recent uppercase/lowercase proxy parity work appears to have covered test fixtures and the subprocess environment without revisiting this resolver.
Environments that produce this shape: test/smoke.test.mjs:40,44 already constructs it. The fixture sets both NO_PROXY: "" and no_proxy: "". It is harmless there because both are empty, but it shows the shape is routine. A trailing space in a .env file, or tooling that exports a declared-but-unset variable, produces it too.
Impact: configureTuiNetworkProxy's noProxy feeds shouldBypassTuiProxy in TuiProxyDispatcher.dispatch, so internal hosts that should go direct are sent to the external proxy. Loopback stays safe because withLoopbackNoProxy always appends it.
Not a duplicate of the open proxy issues:
None concern NO_PROXY precedence inside resolveTuiProxyConfiguration.
Test coverage: no unit test covers resolveTuiProxyConfiguration, and no test file references the module. grep -rn "resolveTuiProxyConfiguration\|shouldBypassTuiProxy\|network-proxy" matches only the source file, main.ts, release/public-source.json, and one unrelated prose mention in a srt-macos.ts comment. The smoke tests exercise it indirectly through main.ts, which is presumably why those proxy variables are in the fixture, but the precedence behavior itself is unverified.
Suggested direction (suggestion only, not a patch): route NO_PROXY through the same helper as the others, for example firstProxyValue(environment, 'NO_PROXY', 'no_proxy')?.value, so empty and whitespace-only values fall through consistently. I am not a repository collaborator, so per CONTRIBUTING.md I am reporting this rather than opening a PR. Happy to supply further detail or regression cases.
Redacted error summary
A no_proxy only (control)
noProxy = internal.corp,localhost,127.0.0.1,::1
shouldBypassTuiProxy('https://internal.corp/v1') = true
B NO_PROXY="" + no_proxy set
noProxy = localhost,127.0.0.1,::1
shouldBypassTuiProxy('https://internal.corp/v1') = false <-- routed through proxy
C NO_PROXY=" " + no_proxy set
noProxy = localhost,127.0.0.1,::1
shouldBypassTuiProxy('https://internal.corp/v1') = false <-- routed through proxy
D HTTPS_PROXY="" + https_proxy set (control)
httpsProxy = http://proxy.corp:8080 <-- lowercase fallback honored
Before submitting
Product or interface
Source build or repository tooling
(The defect is in CLI startup code:
packages/tui/src/cli/main.ts:97callsconfigureTuiNetworkProxy, so all CLI modes share it. I verified it only against the current source at33b259b, not against a released package, so I am not claiming published-build behavior.)Version
Source commit
33b259b, Node.js v24.21.0, pnpm 9.12.0Platform
macOS
OS version and architecture
macOS 15 arm64 (Darwin 25.5.0)
Issue area
Other (proxy / network routing configuration at CLI startup)
Steps to reproduce
resolveTuiProxyConfigurationandshouldBypassTuiProxyare both exported, so this runs against the repository file in place. Nothing is copied or modified.After
pnpm install --frozen-lockfile:Run with
node repro.mjs.Expected and actual behavior
Expected:
no_proxy=internal.corpis honored, sohttps://internal.corp/v1bypasses the proxy, the same way an empty uppercaseHTTPS_PROXYfalls through to lowercasehttps_proxy(case D).Actual: when
NO_PROXYis set but empty or whitespace-only, the lowercaseno_proxyis discarded and the host is routed through the proxy.resolveTuiProxyConfigurationreads four proxy variables. Three go throughfirstProxyValue, which trims, treats empty as unset, and falls back across spellings.NO_PROXYis the only one that does not:??falls through only onnull/undefined, so an emptyNO_PROXYis treated as an authoritative empty bypass list. There is also no.trim(), soNO_PROXY=" "behaves the same way.Why this looks unintended rather than deliberate: an empty
HTTPS_PROXYdoes fall through to lowercasehttps_proxy, becausefirstProxyValuetreats empty as "not set". OnlyNO_PROXYgives an empty value authority. Within a single function the same environment shape is interpreted two different ways.The file has not changed since the initial source import (
git log --oneline -- packages/tui/src/cli/network-proxy.ts→c59cf53only), so the recent uppercase/lowercase proxy parity work appears to have covered test fixtures and the subprocess environment without revisiting this resolver.Environments that produce this shape:
test/smoke.test.mjs:40,44already constructs it. The fixture sets bothNO_PROXY: ""andno_proxy: "". It is harmless there because both are empty, but it shows the shape is routine. A trailing space in a.envfile, or tooling that exports a declared-but-unset variable, produces it too.Impact:
configureTuiNetworkProxy'snoProxyfeedsshouldBypassTuiProxyinTuiProxyDispatcher.dispatch, so internal hosts that should go direct are sent to the external proxy. Loopback stays safe becausewithLoopbackNoProxyalways appends it.Not a duplicate of the open proxy issues:
None concern
NO_PROXYprecedence insideresolveTuiProxyConfiguration.Test coverage: no unit test covers
resolveTuiProxyConfiguration, and no test file references the module.grep -rn "resolveTuiProxyConfiguration\|shouldBypassTuiProxy\|network-proxy"matches only the source file,main.ts,release/public-source.json, and one unrelated prose mention in asrt-macos.tscomment. The smoke tests exercise it indirectly throughmain.ts, which is presumably why those proxy variables are in the fixture, but the precedence behavior itself is unverified.Suggested direction (suggestion only, not a patch): route
NO_PROXYthrough the same helper as the others, for examplefirstProxyValue(environment, 'NO_PROXY', 'no_proxy')?.value, so empty and whitespace-only values fall through consistently. I am not a repository collaborator, so perCONTRIBUTING.mdI am reporting this rather than opening a PR. Happy to supply further detail or regression cases.Redacted error summary
A no_proxy only (control) noProxy = internal.corp,localhost,127.0.0.1,::1 shouldBypassTuiProxy('https://internal.corp/v1') = true B NO_PROXY="" + no_proxy set noProxy = localhost,127.0.0.1,::1 shouldBypassTuiProxy('https://internal.corp/v1') = false <-- routed through proxy C NO_PROXY=" " + no_proxy set noProxy = localhost,127.0.0.1,::1 shouldBypassTuiProxy('https://internal.corp/v1') = false <-- routed through proxy D HTTPS_PROXY="" + https_proxy set (control) httpsProxy = http://proxy.corp:8080 <-- lowercase fallback honoredBefore submitting