Repository navigation
WSL backend hangs at "Connecting to WSL…" (silent exit) when login-shell node is < 22.16 #3709
Description
Summary
On Windows+WSL, the desktop backend hangs forever at "Connecting to WSL…" when the WSL login shell resolves a node that is present but below the engine floor (< 22.16). The server process starts, loads, and exits 0 silently without binding — so the desktop just respawns it in a loop with no diagnostic.
Two independent issues combine to make this a silent, hard-to-diagnose failure.
Environment
- Windows 11 + WSL2 (Ubuntu), backend launched via
wsl.exe -- node … --bootstrap-fd 0 nvmdefault node was v22.12.0; a satisfying v22.21.1 was also installed but not the default- Server
engines.node:^22.16 || ^23.11 || >=24.10
Root cause 1 — import.meta.main guard fails silently on Node < 22.16
apps/server/src/bin.ts gates all startup on import.meta.main:
if (import.meta.main) {
Command.run(cli, { version: packageJson.version }).pipe(/* … */ NodeRuntime.runMain);
}import.meta.main was added in Node 22.16.0. On 22.12.0 it is undefined, so the guard is falsy: the module loads its entire import graph and the process exits 0 with no output and no error. Verified:
$ node -v # v22.12.0
$ node -e 'x' --input-type=module ... # import.meta.main === undefined
# server run under 22.12.0 -> loads, exits 0, never creates a socket (strace: zero bind/listen)
# server run under 22.21.1 -> binds and serves /.well-known/t3/environment correctly
Because it exits 0 cleanly, the desktop's restart loop keeps respawning it and the UI shows "Connecting to WSL…" indefinitely. Suggestion: detect an unsupported/undefined import.meta.main Node and fail loudly (clear stderr message + non-zero exit) so the desktop can surface it, e.g. a robust entrypoint check plus an explicit engine assertion:
import { pathToFileURL } from "node:url";
const isEntrypoint =
import.meta.main ??
(process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href);
if (isEntrypoint) { /* … */ }Root cause 2 — ensure_remote_node_path won't upgrade away from an unsatisfying node
packages/ssh/src/tunnel.ts REMOTE_NODE_ENV_SCRIPT: the early-return correctly checks node present and satisfies engine, but every version-manager fallback (mise/fnm/nodenv/nvm, and the nvm version scan) is gated on ! command -v node — i.e. "no node at all", not "no satisfying node". So when a login shell already exposes a node that fails the engine range (e.g. nvm default 22.12.0, or a system /usr/bin/node), the fallbacks are skipped and ensure_remote_node_path execs the unsatisfying node anyway (… || true).
Minimal fix — introduce a remote_node_ready helper and use it for the gates:
remote_node_ready() {
command -v node >/dev/null 2>&1 && remote_node_satisfies_engine >/dev/null 2>&1
}
ensure_remote_node_path() {
if remote_node_ready; then
return 0
fi
# …prepend_path_if_dir calls unchanged…
if ! remote_node_ready && command -v mise >/dev/null 2>&1; then …; fi
if ! remote_node_ready && command -v fnm >/dev/null 2>&1; then …; fi
if ! remote_node_ready && command -v nodenv >/dev/null 2>&1; then …; fi
if [ -s "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh"
if ! remote_node_ready && command -v nvm >/dev/null 2>&1; then
nvm use --silent default >/dev/null 2>&1 || nvm use --silent node >/dev/null 2>&1 || nvm use --silent --lts >/dev/null 2>&1 || true
fi
fi
if ! remote_node_ready && [ -d "$NVM_DIR/versions/node" ]; then
for T3_NODE_BIN in "$NVM_DIR"/versions/node/*/bin; do
[ -x "$T3_NODE_BIN/node" ] && PATH="$T3_NODE_BIN:$PATH" && export PATH
done
fi
remote_node_ready
}(The nvm version scan could additionally prefer a satisfying version rather than the last glob match, but the gate change alone lets it fall through to nvm's default and the other managers.)
Impact / workaround
Workaround: make the login-shell default node satisfy the range (nvm alias default 22.21.1) — confirmed fixes it. But the failure mode is invisible: no log line, clean exit, infinite "Connecting to WSL…".
Activity
arturskruze commentedon Jul 5, 2026
Opened a small PR for root cause 2 (the
ensure_remote_node_pathgate): #3710 — it makes the version-manager fallbacks fire when the present node fails the engine range, so a satisfying node that's already installed gets selected. Left thebin.tsloud-failure (root cause 1) out of it to keep the PR focused; happy to follow up on that separately.Hamzaalhaj123 commentedon Aug 5, 2026
I haven't been able to use t3code reliably on my wsl projects since day one, am i missing something?
maria-rcks commentedon Oct 9, 2026
Note
This is an automated response from a bot acting on maria's behalf.
This issue has had no activity since 2026-08-05.
Is it still relevant on the current version? If so, your version and OS would help.