You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(runtime): testProxyConnection hangs behind a proxy and bots/proxied-fetch leaks dispatchers — abort-before-close teardown survives the #5900 transport fix #5978
PR #5900 (open at the time of writing) fixes the shared transport for #5898. Two other call sites build their own dispatcher via buildProxyDispatcher() and dispose it themselves, so the same abort-before-close hang survives that fix:
packages/runtime/src/network/proxy-test.ts (testProxyConnection, behind the Desktop "Test current configuration" button). On success its finally runs controller.abort() and then await disposeDispatcher(timedOut), which closes the dispatcher — the same ordering fix(runtime): bound proxied fetch transport teardown #5900 reordered inside the transport. At fix(runtime): bound proxied fetch transport teardown #5900's head (dcd2be3), testProxyConnection against a real proxy is still pending after 25s, long past its 8s probe timeout, and in the Desktop app the button spins forever. The call also occupies the main-process serialized settings lane (enqueue in runtime-host-settings-ipc-main.ts), so once it wedges, subsequent settings:get/settings:update calls queue behind it; that lane is process-local, so a restart clears it. On main pre-fix(runtime): bound proxied fetch transport teardown #5900 the blast radius was larger: host-side policy operations hung as well and silently dropped proxy-setting writes. That wider freeze is how the underlying defect originally surfaced.
packages/runtime/src/bots/proxied-fetch.ts. The success path fire-and-forgets void disposeDispatcher(false) — callers are unblocked, but the dispatcher's destroy never settles, so it leaks per request. Its error path awaits the close after aborting (same shape; not yet reproduced).
How to reproduce
Through the product: enable a network proxy, then Settings → Proxy server → "Test current configuration" — the button spins indefinitely. Standalone: bundle proxy-test.ts with its network/ closure (esbuild, undici 8.11.2) and run testProxyConnection against any reachable HTTP proxy — the returned promise never settles.
Node.js version: 24.18.1; Electron 43.4.1 embedded Node; undici 8.11.2
Logs, screenshots, or additional context
Measurement: I bundled proxy-test.ts with its network/ closure (esbuild, undici 8.11.2) and raced testProxyConnection against a 25s timer, against a real HTTP proxy. Two consecutive runs both stayed pending past 25s; the same two-request probe through the #5900 transport settles in milliseconds. The hang is deterministic, not intermittent. Observed both on a packaged desktop build from main and on a dev build at dcd2be3, so this is not a regression introduced by #5900 — that PR simply does not reach these call sites.
Suggested direction
Maintainers' call. The #5900 pattern (destroy before abort, bounded grace) looks directly applicable to proxy-test.ts; proxied-fetch.ts needs a separate decision for the leak and the error path. A regression test in packages/runtime/src/network/__tests__/proxy-test.test.ts — perform a successful request through a proxied dispatcher, then assert testProxyConnection settles within a bound — would cover the first call site.
Provenance
Generative tooling contributed substantively to this report (investigation and drafting with an AI coding assistant); all measurements were verified against the repository and a locally built desktop app.
What happened
PR #5900 (open at the time of writing) fixes the shared transport for #5898. Two other call sites build their own dispatcher via
buildProxyDispatcher()and dispose it themselves, so the same abort-before-close hang survives that fix:packages/runtime/src/network/proxy-test.ts(testProxyConnection, behind the Desktop "Test current configuration" button). On success itsfinallyrunscontroller.abort()and thenawait disposeDispatcher(timedOut), which closes the dispatcher — the same ordering fix(runtime): bound proxied fetch transport teardown #5900 reordered inside the transport. At fix(runtime): bound proxied fetch transport teardown #5900's head (dcd2be3),testProxyConnectionagainst a real proxy is still pending after 25s, long past its 8s probe timeout, and in the Desktop app the button spins forever. The call also occupies the main-process serialized settings lane (enqueueinruntime-host-settings-ipc-main.ts), so once it wedges, subsequentsettings:get/settings:updatecalls queue behind it; that lane is process-local, so a restart clears it. Onmainpre-fix(runtime): bound proxied fetch transport teardown #5900 the blast radius was larger: host-side policy operations hung as well and silently dropped proxy-setting writes. That wider freeze is how the underlying defect originally surfaced.packages/runtime/src/bots/proxied-fetch.ts. The success path fire-and-forgetsvoid disposeDispatcher(false)— callers are unblocked, but the dispatcher's destroy never settles, so it leaks per request. Its error path awaits the close after aborting (same shape; not yet reproduced).How to reproduce
Through the product: enable a network proxy, then Settings → Proxy server → "Test current configuration" — the button spins indefinitely. Standalone: bundle
proxy-test.tswith itsnetwork/closure (esbuild, undici 8.11.2) and runtestProxyConnectionagainst any reachable HTTP proxy — the returned promise never settles.Environment
main3597abe (packaged desktop build) and fix(runtime): bound proxied fetch transport teardown #5900 head dcd2be3 (dev build) — both affectedLogs, screenshots, or additional context
Measurement: I bundled
proxy-test.tswith itsnetwork/closure (esbuild, undici 8.11.2) and racedtestProxyConnectionagainst a 25s timer, against a real HTTP proxy. Two consecutive runs both stayed pending past 25s; the same two-request probe through the #5900 transport settles in milliseconds. The hang is deterministic, not intermittent. Observed both on a packaged desktop build frommainand on a dev build at dcd2be3, so this is not a regression introduced by #5900 — that PR simply does not reach these call sites.Suggested direction
Maintainers' call. The #5900 pattern (destroy before abort, bounded grace) looks directly applicable to
proxy-test.ts;proxied-fetch.tsneeds a separate decision for the leak and the error path. A regression test inpackages/runtime/src/network/__tests__/proxy-test.test.ts— perform a successful request through a proxied dispatcher, then asserttestProxyConnectionsettles within a bound — would cover the first call site.Provenance
Generative tooling contributed substantively to this report (investigation and drafting with an AI coding assistant); all measurements were verified against the repository and a locally built desktop app.