Skip to content

[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

@vazome

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

Steps to reproduce

  1. Install T3 Code Alpha 0.0.28 on Windows 11 x64 with WSL2 present (Ubuntu-24.04, default distro, already running).
  2. Install a Linux Node in the distro that satisfies the server engine range (mine: Node v22.20.0 via Volta).
  3. In Settings, enable the WSL backend and select the distro.
  4. Wait on the "Connecting to WSL…" splash.
  5. Observe as it never resolves, no matter how long you wait.
  6. Inspect %USERPROFILE%\.t3\userdata\logs\server-child.log and 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:

wsl.exe -d Ubuntu-24.04 --exec env "PATH=/home/<user>/.volta/bin:..." \
  /home/<user>/.volta/bin/node \
  /mnt/c/Users/<user>/AppData/Local/Programs/t3code/resources/app.asar.unpacked/apps/server/dist/bin.mjs \
  --bootstrap-fd 0

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.waitForHttpReady runs for exactly 60s (60009ms / 60020ms observed), exits Failure with BackendTimeoutError, 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

17:31:07.622  backend child process session start — pid=31260 port=3773
   ...78 seconds with no output at all...
17:32:25.765  first stderr line
17:32:26.246  INFO (#1): Running all migrations...
17:32:26.271  INFO (#1): Migrations ran successfully  migrations: []
17:32:27.818  INFO (#172): Listening on http://0.0.0.0:3773

Once it starts, everything finishes in ~2s — the whole cost is module loading before any app code runs.

Preflight is not implicated:

Step Duration
wslPreflight 924ms
ensureNodePty 666ms
resolveWsl 1093ms
desktop.startup 2818ms

Some measurements inside WSL on warm cache

Measurement Result
apps/server/dist 797 files, 68 MB
app.asar.unpacked 29,234 files, 993 MB (of which node_modules 28,437 files, 925 MB)
import() of bin.mjs from /mnt/c 31.5s (does not start the server; import.meta.main is false)
cp -r dist → ext4 44.8s cold / 12.4s warm (~5.5 MB/s)
single bin.mjs read 0.043s

During that 31.5s import, CPU was user 2.181s + sys 1.457s which signals that the process is entirely blocked on I/O and not CPU.

Related, but different source of the issue

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

# desktop.trace.ndjson — probe expires and never retries
"httpReadiness.timedOut",{"baseUrl":"http://172.31.213.87:3773/",
 "requestUrl":"http://172.31.213.87:3773/.well-known/t3/environment",
 "timeoutMs":60000,"intervalMs":100,"probeTimeoutMs":1000,"attempts":54,
 "lastFailure":{"attempt":53,"cause":{"_tag":"BackendTimeoutError",
 "message":"Timed out waiting for backend readiness at http://172.31.213.87:3773/.well-known/t3/environment."}}}

# span durations for two failing launches
60020 ms  17:29:27.371  shared.httpReadiness.waitForHttpReady   <-- Failure
60009 ms  17:31:07.625  shared.httpReadiness.waitForHttpReady   <-- Failure
# every http.client GET span is ~1000-1017ms, i.e. each probe hits probeTimeoutMs:1000
# against a port nothing is bound to yet

# server-child.log — backend listens 20s AFTER the desktop stopped waiting
17:31:07.622  backend child process session start — pid=31260 port=3773 cwd=C:\Users\<user>
17:32:26.246  INFO (#1): Running all migrations...
17:32:26.271  INFO (#1): Migrations ran successfully  migrations: []
17:32:27.818  INFO (#172): Listening on http://0.0.0.0:3773

# backend healthy while the splash was still spinning
GET http://127.0.0.1:3773/.well-known/t3/environment     -> 200 (0.0079s)
GET http://172.31.213.87:3773/.well-known/t3/environment -> 200
{"environmentId":"<redacted>","label":"<redacted>","platform":{"os":"linux","arch":"x64"},
 "serverVersion":"0.0.28","capabilities":{"repositoryIdentity":true}}

# ss -lntp inside WSL
LISTEN 0 511 0.0.0.0:3773 users:(("node",pid=6464,fd=22))

# boot time per run, same machine
runId          config                                              boot     result
0bf624a8c178   Windows-native backend (before enabling WSL)        15.1s    connected
dc9b41b939b3   WSL backend, baseline                               73.0s    timed out
93eed0e16d41   WSL backend, baseline                               81.0s    timed out
b499417b3f3f   WSL backend, baseline                               80.2s    timed out
5ef8b2b0b613   + Defender exclusions (install dir, wsl.exe,
                 vmmemWSL, wslservice.exe)                         71.1s    timed out
3df58a07f706   + server dist staged on ext4, bind-mounted over
                 the /mnt/c path                                   49.7s    CONNECTED

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/c path, so Node reads it locally instead of across the bridge. Though not ideal and you will see why:

DIST="/mnt/c/Users/<user>/AppData/Local/Programs/t3code/resources/app.asar.unpacked/apps/server/dist"

sudo mkdir -p /opt/t3-dist
sudo rsync -a --delete "$DIST/" /opt/t3-dist/
sudo mount --bind /opt/t3-dist "$DIST"

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:

  • Only dist (68 MB) is staged; the remaining node_modules (925 MB, 28,437 files) is still read over /mnt/c on every launch.
  • The mount does not survive wsl --shutdown.
  • While it is active WSL keeps seeing the staged copy, so a Windows-side app update is masked until it is re-synced.

Things that did not work

  • Restarting the app, including with the distro already warm — 80s every time.
  • Windows Defender exclusions alone — ~9s in a real boot (80.2s → 71.1s), not enough.
  • Raising the deadline — there is no user-side way. The WSL path calls waitForHttpReadyShared({ timeoutMs: Duration.toMillis(timeout), ... }) with a built-in constant.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Jul 25, 2026
  2. carlospedreira commented on Jul 28, 2026

    @carlospedreira

    I’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.ts from:

    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/c is particularly slow.

  3. 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
  4. moinmir commented on Aug 9, 2026

    @moinmir

    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 bare import() of bin.mjs is 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-server and ~/.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.gz and extracts it into ~/.t3/runtime inside the distro.

  5. antoineol commented on Aug 10, 2026

    @antoineol

    Here is a more efficient workaround that, on my machine, reduces both cache-building and startup times to a few seconds.

    • Caches both dist and node_modules (to avoid cross-OS reads)
    • Uses fast tar.exe | tar streaming instead of rsync
    • 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.
    
  6. t3-code commented on Aug 20, 2026

    @t3-code
    Contributor

    closing the reported infinite-wait defect as completed. #5526 keeps probing through slow WSL cold starts instead of abandoning startup after the initial deadline.

  7. alekb commented on Aug 22, 2026

    @alekb

    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, and getDistroIp. It correctly selects Ubuntu’s eth0 address, 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: 54
    

    After 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/environment
    

    This 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions