Repository navigation
[Bug]: Unusable. WSL backend readiness timesout at 60s because the server bundle loads over /mnt/c (takin 80s to boot) and app stuck on "Connecting to WSL…" forever #4535
Description
Activity
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Jul 25, 2026 carlospedreira commented
on Jul 28, 2026 More actionsI’m seeing the same issue on my system, although the WSL backend takes even longer to become ready. In my case, it takes approximately 117 seconds to load.
I was able to work around it locally by changing the default readiness timeout in
apps/desktop/src/backend/DesktopBackendManager.tsfrom:const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(1);
to:
const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3);
After making that change, T3 Code connects to the WSL backend successfully.
It would be helpful to increase this timeout upstream, since one minute may not be sufficient on systems where loading the backend through
/mnt/cis particularly slow.- changed the title
[-][Bug]: WSL backend readiness timesout at 60s because the server bundle loads over /mnt/c (takin 80s to boot) and app stuck on "Connecting to WSL…" forever[/-][+][Bug]: Unusable. WSL backend readiness timesout at 60s because the server bundle loads over /mnt/c (takin 80s to boot) and app stuck on "Connecting to WSL…" forever[/+]on Aug 2, 2026 I'm facing the same issue. Some newer numbers from my side, on a smaller build (14,667 files / 224 MB unpacked) and a warm VM:
- Boot from
/mnt/c: 16.8s. A bareimport()ofbin.mjsis 15.3-15.7s, and repeat runs give the same number. The Windows file cache does not help at all. The issue here seems to be the large number of files. - Same bundle on ext4 inside the distro: 1.25s boot, 0.6s import.
- Copying the file tree into the distro takes about 2 minutes. Moving the same 224 MB as one tar file and extracting it on ext4 takes 4.4s. Large single files read at 150-180 MB/s over the bridge.
VS Code and Zed already solve this problem. Both put one archive inside the distro and run the server from there, at
~/.vscode-serverand~/.zed_server. I tested it locally and this should not meaningfully slow down updates.#5769 does the same thing, and looks mostly correct to me. It ships the runtime as one
tar.gzand extracts it into~/.t3/runtimeinside the distro.- Boot from
Here is a more efficient workaround that, on my machine, reduces both cache-building and startup times to a few seconds.
- Caches both
distandnode_modules(to avoid cross-OS reads) - Uses fast
tar.exe | tarstreaming instead ofrsync - Automatically handles T3 updates and WSL restarts
- Preserves cache mounts when starting from a stopped WSL distro
It is packaged as a prompt for easy installation.
Hope it can help.
Prompt
Install and verify this compact T3 Code WSL cache workaround. Do not merely explain it. This is an unofficial temporary workaround tested through T3 Code 0.0.33. First inspect T3’s settings and installation. Adapt only paths, executable names, distro names, or package names that genuinely differ. Preserve user/project data. Do not replace the official executable, shortcut, or disable/restart WSL. Create `~/.local/bin/t3code-wsl-cache` in T3’s configured WSL distro with LF endings and mode `755`: ```bash #!/usr/bin/env bash set -euo pipefail [[ $EUID == 0 ]] || exit 1 ver=${1:-}; sha=${2:-}; user=${3:-}; home=${4:-} [[ $ver =~ ^[0-9A-Za-z._-]+$ && $sha =~ ^[0-9a-fA-F]{64}$ && $home == /* ]] id "$user" >/dev/null [[ $(getent passwd "$user" | cut -d: -f6) == "$home" ]] cd /mnt/c win_local=$(cmd.exe /d /c echo %LOCALAPPDATA% | tr -d '\r') win_dir=$(cmd.exe /d /c echo %WINDIR% | tr -d '\r') src=$(wslpath -u "$win_local")/Programs/t3code/resources/app.asar.unpacked wtar=$(wslpath -u "$win_dir")/System32/tar.exe cache=$home/.t3/runtime-cache/t3code-$ver dist=$src/apps/server/dist; mods=$src/node_modules cdist=$cache/app.asar.unpacked/apps/server/dist cmods=$cache/app.asar.unpacked/node_modules marker=$cache/.source-sha256 node=$(runuser -u "$user" -- env HOME="$home" bash -lc 'command -v node') [[ -x $node ]] group=$(id -gn "$user") install -d -o "$user" -g "$group" "$home/.t3/runtime-cache" exec 9>"$home/.t3/runtime-cache/.t3code-cache.lock"; flock 9 mount_one() { local source=$1 target=$2 if mountpoint -q "$target"; then [[ $(findmnt -rn -M "$target" -o FSROOT) == "$(readlink -f "$source")" ]] && return umount "$target" fi mount --bind "$source" "$target" } mount_cache() { [[ -f $cdist/bin.mjs && -d $cmods ]] || return 1 install -d "$dist" "$mods" mount_one "$cdist" "$dist" mount_one "$cmods" "$mods" } if [[ -f $marker && $(<"$marker") == ${sha,,} ]] && mount_cache; then echo "T3 Code WSL cache ready ($ver)"; exit fi mountpoint -q "$mods" && umount "$mods" || true mountpoint -q "$dist" && umount "$dist" || true [[ -f $dist/bin.mjs ]] stage=$cache.new.$$ trap 'rm -rf -- "$stage"' EXIT install -d "$stage/app.asar.unpacked" "$wtar" -C "$(wslpath -w "$src")" -cf - apps/server/dist node_modules | tar --no-same-owner --no-same-permissions \ -C "$stage/app.asar.unpacked" -xf - smods=$stage/app.asar.unpacked/node_modules sdist=$stage/app.asar.unpacked/apps/server/dist for m in node-pty ffi-rs; do [[ ! -f $smods/$m/package.json ]] || "$node" -e "require('$smods/$m')" done "$node" --input-type=module -e "import('file://$sdist/bin.mjs')" chown -R "$user:$group" "$stage" rm -rf -- "$cache"; mv "$stage" "$cache"; trap - EXIT printf '%s\n' "${sha,,}" >"$marker" mount_cache echo "T3 Code WSL cache rebuilt ($ver)" ``` Create `%USERPROFILE%\.t3\t3code-fast-wsl.ps1`: ```powershell $ErrorActionPreference = "Stop" $dir = Join-Path $env:LOCALAPPDATA "Programs\t3code" $exe = Join-Path $dir "T3 Code (Alpha).exe" $wsl = Join-Path $env:WINDIR "System32\wsl.exe" $log = Join-Path $env:USERPROFILE ".t3\t3code-fast-wsl.log" if (Get-Process -Name "T3 Code (Alpha)" -ErrorAction SilentlyContinue) { Start-Process $exe -WorkingDirectory $dir exit } try { $cfg = Get-Content -Raw (Join-Path $env:USERPROFILE ".t3\userdata\desktop-settings.json") | ConvertFrom-Json $distro = [string]$cfg.wslDistro if (!$distro) { throw "No T3 Code WSL distribution is configured." } $userFile = [IO.Path]::GetTempFileName() $homeFile = [IO.Path]::GetTempFileName() try { $userProcess = Start-Process $wsl ` -ArgumentList @("-d", $distro, "--exec", "id", "-un") ` -NoNewWindow -Wait -PassThru -RedirectStandardOutput $userFile $homeProcess = Start-Process $wsl ` -ArgumentList @("-d", $distro, "--exec", "printenv", "HOME") ` -NoNewWindow -Wait -PassThru -RedirectStandardOutput $homeFile $user = (Get-Content -Raw $userFile).Trim() $linuxHome = (Get-Content -Raw $homeFile).Trim() } finally { Remove-Item $userFile,$homeFile -ErrorAction SilentlyContinue } if ($userProcess.ExitCode -ne 0 -or $homeProcess.ExitCode -ne 0 -or !$user -or !$linuxHome) { throw "Cannot resolve the WSL user." } $ver = (Get-Item $exe).VersionInfo.FileVersion $sha = (Get-FileHash (Join-Path $dir "resources\app.asar") -Algorithm SHA256).Hash $helper = "$linuxHome/.local/bin/t3code-wsl-cache" $args = @("-d", $distro, "-u", "root", "--exec", $helper, $ver, $sha, $user, $linuxHome) # Keep a cold distro alive until Electron starts its WSL backend. $keepAlive = Start-Process $wsl ` -ArgumentList @("-d", $distro, "--exec", "sleep", "60") ` -NoNewWindow -PassThru $p = Start-Process $wsl -ArgumentList $args -WorkingDirectory $dir ` -NoNewWindow -Wait -PassThru ` -RedirectStandardOutput $log -RedirectStandardError "$log.err" if ($p.ExitCode) { throw "WSL cache failed (exit $($p.ExitCode)); see $log.err" } Start-Process $exe -WorkingDirectory $dir } catch { $_ | Out-String | Set-Content $log Add-Type -AssemblyName PresentationFramework [System.Windows.MessageBox]::Show( "T3 Code WSL cache failed. See $log", "T3 Code" ) | Out-Null } ``` Important PowerShell details: - Use `$linuxHome`, not `$home`, because `$HOME` is read-only and variable names are case-insensitive. - Do not add escaped quotes around individual `Start-Process -ArgumentList` values unless inspection proves they are required; they may be passed literally. Create a dedicated Start-menu shortcut named `T3 Code - WSL cache`. Do not replace the official shortcut. Target Windows PowerShell with: ```text -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "<resolved-user-profile>\.t3\t3code-fast-wsl.ps1" ``` Close T3 Code before each verification launch. Then: 1. Launch through the new shortcut and measure the cold rebuild. 2. Verify both bind mounts originate from the expected ext4 cache. 3. Verify the backend runs as a Linux Node process. 4. Verify no visible terminal remains; the temporary `sleep 60` WSL keepalive exits automatically while the backend and bind mounts remain. 5. Close T3 Code, unmount only the two test bind mounts, then launch through the shortcut again. 6. Measure the warm launch and confirm the log says `cache ready`, mounts are restored, cache identity is unchanged, and no new staging directory exists. Report the detected version, SHA-256, resolved paths, validation results, cold/warm timings, and exact rollback commands. If the installed release has an incompatible layout, stop without modifying unrelated files.- Caches both
closing the reported infinite-wait defect as completed. #5526 keeps probing through slow WSL cold starts instead of abandoning startup after the initial deadline.
Confirmed this still reproduces on T3 Code Desktop 0.0.33.
Environment:
- Windows 11 x64, build 26200.9168
- WSL 2.7.10.0
- Ubuntu under WSL2
- Node v22.22.3 at
/usr/bin/node - No mirrored-networking
.wslconfig - No Docker networks present
T3’s WSL preflight succeeds, including
probeDistros,ensureNodePty, andgetDistroIp. It correctly selects Ubuntu’seth0address,172.18.68.156.The desktop trace then records:
shared.httpReadiness.waitForHttpReady durationMs: 60020.2931 BackendReadinessTimeoutError http://172.18.68.156:3773/.well-known/t3/environment attempts: 54After the desktop stops checking, the backend is healthy and listening:
LISTEN 0 511 0.0.0.0:3773 users:(("node",pid=5066,fd=22))Both requests below return HTTP 200 with server version 0.0.33:
# Inside WSL curl http://127.0.0.1:3773/.well-known/t3/environment # From Windows curl.exe http://172.18.68.156:3773/.well-known/t3/environmentThis rules out an unsupported Node version, Docker bridge-IP selection, and Windows-to-WSL connectivity. The desktop remains indefinitely on “Connecting to WSL…” because it does not resume readiness checks after the fixed 60-second deadline.
- added a commit that references this issue
on Aug 27, 2026
Before submitting
Area
apps/desktop
Steps to reproduce
%USERPROFILE%\.t3\userdata\logs\server-child.logand desktop.trace.ndjson.The backend reaches "Listening on http://0.0.0.0:3773" about 20 seconds AFTER the desktop's readiness probe already gave up.
Restarting the app reproduces it every time, including with the distro already warm. Because wslOnly persists in desktop-settings.json, restarting and reinstalling both reproduce it, so recovery requires hand-editing that file.
The trigger is how long the server bundle takes to load over the WSL<->Windows filesystem bridge. The desktop runs the Windows-side bundle in place rather than staging it in the distro:
so every module read crosses drvfs/9p on every launch.
Expected behavior
The desktop should connect to a backend that becomes healthy around 20 seconds after the readiness budget expires, rather than stopping its probe permanently. Failing that, it should surface an error and a recovery path instead of an indefinite "Connecting to WSL…" splash with no message.
Actual behavior
shared.httpReadiness.waitForHttpReadyruns for exactly 60s (60009ms / 60020ms observed), exitsFailurewithBackendTimeoutError, and never probes again. The splash spins forever.The backend is fine. It starts, runs migrations, listens on
0.0.0.0:3773, and answers HTTP 200 continuously — nothing is listening for it anymore. Waiting longer never helps, because there is no retry after the deadline.Boot is 73–81s against the 60s budget.
Timeline
Once it starts, everything finishes in ~2s — the whole cost is module loading before any app code runs.
Preflight is not implicated:
wslPreflightensureNodePtyresolveWsldesktop.startupSome measurements inside WSL on warm cache
apps/server/distapp.asar.unpackednode_modules28,437 files, 925 MB)import()ofbin.mjsfrom/mnt/cimport.meta.mainis false)cp -r dist→ ext4bin.mjsreadDuring that 31.5s import, CPU was
user 2.181s + sys 1.457swhich signals that the process is entirely blocked on I/O and not CPU.Related, but different source of the issue
BackendTimeoutError, but on the Windows-native backend, triggered by slow provider/environment discovery (checkCursorProviderStatus~22.7s, command resolution ~28.5s), and there the desktop shuts the backend down and exits. Here it is the WSL backend, the delay is module loading over/mnt/cbefore any provider code runs, and the desktop stays up spinning while the backend keeps running healthy.< 22.16. Not this: my Node isv22.20.0and the server starts correctly.Impact
Blocks work completely
Version or commit
T3 Code Alpha 0.0.28
Environment
Windows 11 build 26200.8875 x64, WSL 2.7.10.0, Ubuntu-24.04, Node v22.20.0 in WSL via Volta
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
So the only working solution rightnow is to copy the server bundle onto ext4 inside the distro and bind-mount it over the
/mnt/cpath, so Node reads it locally instead of across the bridge. Though not ideal and you will see why:After this the backend booted in 49.7s and the app connected.
It works, but startup is still slow and rather close the deadline, 49.7s against a 60s deadline, so 10s to spare. Caveats:
dist(68 MB) is staged; the remainingnode_modules(925 MB, 28,437 files) is still read over/mnt/con every launch.wsl --shutdown.Things that did not work
waitForHttpReadyShared({ timeoutMs: Duration.toMillis(timeout), ... })with a built-in constant.