Skip to content

Desktop on Windows sends its bootstrap token to a WSL background service on the same port (invalid_credential on first launch) #16826

Description

@FranciscoMessina

What happened

On Windows, the desktop app (nightly) often shows this error the first time it is opened. Closing it and opening it again usually works.

PrimaryEnvironmentRequestError
    at async Object.beforeLoad (t3code://app/assets/main-CRgQa3HH.js:20:72083)
Caused by:
Error: Error invoking remote method 'desktop:get-local-environment-bearer-token': DesktopLocalEnvironmentAuthSessionBootstrapError: Failed to create the local desktop bearer session.

Diagnosis

The desktop sends its bootstrap token to a different T3 Code server: a background service running inside WSL2, which Windows reaches through WSL localhost forwarding on the same port.

  1. The machine has a T3 Code background service inside WSL2 (Ubuntu): t3code.service, a systemd user unit running t3 serve with T3CODE_HOME=~/.t3 on the Linux side. It listens on the default port, 127.0.0.1:3773.
  2. On a cold start, WSL is still booting when the desktop runs resolveDesktopBackendPort (apps/desktop/src/app/DesktopApp.ts:75). At that moment 127.0.0.1, 0.0.0.0 and :: are all free on 3773, so the desktop picks 3773 and its Windows backend binds 0.0.0.0:3773.
  3. About 2 s later, WSL's localhost forwarding starts and wslrelay.exe binds 127.0.0.1:3773 on Windows. Windows allows both binds, and a connection to 127.0.0.1:3773 goes to the more specific listener, which is the relay to the WSL service.
  4. The desktop's POST http://127.0.0.1:3773/oauth/token reaches the WSL service. That service has a different desktop bootstrap secret, so it returns invalid_credential. isTransientBearerBootstrapError (apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts:27) treats that as final, so the request is not retried and the renderer crashes in beforeLoad.
  5. On the next launch, WSL is already running and the relay already holds 127.0.0.1:3773. The scan skips to 3774 and everything works. That is why it seems to fail only "on first open".

Every launch logged in desktop.trace.ndjson (Oct 4–7, about 20 launches) fits this pattern:

  • Every launch where the desktop picked port 3773 failed.
  • Every launch on 3774 or 3775 succeeded.

While the bug was happening, Windows showed two listeners on 3773:

LocalAddress LocalPort OwningProcess
127.0.0.1    3773      35824   # wslrelay.exe --mode 2 (started 09:54:11 local)
0.0.0.0      3773      37356   # desktop backend (started 09:54:09 local)

and inside WSL:

LISTEN 127.0.0.1:3773  users:(("node-MainThread",pid=594))   # t3 serve (t3code.service, 0.0.44)

Possible fixes, any one of which would help:

  • Have the desktop backend bind 127.0.0.1 when network access is off, so a later 127.0.0.1 bind by another process fails instead of shadowing it.
  • Check after readiness that the server answering on the chosen port is the one the desktop launched. For example, compare the environment id or PID reported by /.well-known/t3/environment, then re-select the port or restart the backend if it isn't.
  • On Windows with WSL, wait for WSL localhost forwarding to settle before scanning, or skip ports a WSL distro is listening on.
  • At minimum, report "another T3 Code server is answering on this port" instead of a generic bootstrap failure.

Steps to reproduce

  1. On Windows 11 with WSL2 (default NAT networking with localhost forwarding), install the T3 Code background service inside a WSL distro with the default port: t3 service install inside WSL, listening on 127.0.0.1:3773.
  2. Run wsl --shutdown, so the distro and its systemd service start cold the next time WSL is used.
  3. Launch the T3 Code desktop app on Windows. If the WSL backend is enabled, the desktop starts WSL during launch, and the service starts with it.
  4. The desktop selects port 3773 before wslrelay binds it. Its /oauth/token exchange gets invalid_credential from the WSL service, and the error screen appears.
  5. Quit and relaunch. The desktop now selects 3774 and works.

Version

Desktop T3 Code (Nightly) 0.0.46-nightly.20261006.2735 (fd1c338). The WSL background service was t3 0.0.44, and the Windows CLI is 0.0.45.

Environment

Windows 11 Pro 10.0.26300 x64, WSL2 Ubuntu (systemd=true), Node v26.8.2

Evidence

# desktop.trace.ndjson, 2026-10-07 (UTC)
12:54:09.842 resolveDesktopBackendPort -> 3773 ("selected backend port via sequential scan")
12:54:09.852 desktop.backendInstance.start {"id":"primary"}
12:54:10.006..12:54:17.742 GET http://127.0.0.1:3773/.well-known/t3/environment  (transport errors, then Success)
12:54:18.330 POST http://127.0.0.1:3773/oauth/token
12:54:18.327 clientRuntime.authorization.bootstrapRemoteBearerSession  Failure  EnvironmentAuthInvalidError: The environment rejected this client's credentials (invalid_credential).
12:54:18.326 desktop.localEnvironmentAuth.getBearerToken               Failure  DesktopLocalEnvironmentAuthSessionBootstrapError
  (repeated 3x within 600 ms)

# Launch port vs. outcome, desktop.trace.ndjson{,.1..10}
2026-10-04T18:00:07 port 3773 -> BEARER FAIL     2026-10-04T18:00:28 port 3774 -> ok
2026-10-05T12:57:35 port 3773 -> BEARER FAIL     2026-10-05T12:58:02 port 3774 -> ok
2026-10-06T17:35:31 port 3773 -> BEARER FAIL     2026-10-06T17:35:48 port 3775 -> ok
2026-10-06T22:01:20 port 3773 -> BEARER FAIL     2026-10-06T22:01:33 port 3774 -> ok
2026-10-07T12:54:09 port 3773 -> BEARER FAIL
(no launch on 3773 succeeded; no launch on 3774/3775 failed)

Related issues

#12918 has the same error screen, but its cause is transient 5xx and reset errors, and its fix retries only those. A rejected credential stays final, so that fix does not cover this case. #6097 covers the desktop and a background service on the same OS sharing a port and database. Here the service runs in WSL with its own T3 home, and the problem is the WSL relay taking over the desktop's port after the port was chosen.

Fix applied or workaround

I moved the WSL service off the default port with a systemd drop-in, which survives t3 update rewriting the unit, and updated it to 0.0.45:

mkdir -p ~/.config/systemd/user/t3code.service.d
printf '[Service]\nEnvironment=T3CODE_PORT=4773\n' > ~/.config/systemd/user/t3code.service.d/port.conf
systemctl --user daemon-reload
t3 update --yes

Afterwards only the desktop backend listens on 3773 on Windows. Setting T3CODE_PORT for the desktop app on the Windows side also works.

Filed by

Claude Code (claude-opus-5-5) via t3 triage

Activity

  1. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed diagnosis. I checked it against the code, and it holds up.

    Root cause

    • resolveDesktopBackendPort (apps/desktop/src/app/DesktopApp.ts:75) picks the first port that is free on 127.0.0.1, 0.0.0.0 and :: (DesktopApp.ts:40). It only checks once, at startup, so it can't see a WSL relay that binds the port about 2 s later.
    • The local URLs always use 127.0.0.1 (apps/desktop/src/backend/DesktopServerExposure.ts:110-111). The bind host is 127.0.0.1 only in local-only mode; any other exposure mode binds 0.0.0.0 (DesktopServerExposure.ts:113-131). Your 0.0.0.0:3773 listener means network access was on for this launch. In that mode, a later, more specific 127.0.0.1:3773 bind by wslrelay.exe takes the desktop's own loopback traffic.
    • Readiness only polls /.well-known/t3/environment until it gets an HTTP response (apps/desktop/src/backend/DesktopBackendManager.ts:70,381-397). It doesn't check that the server answering is the backend the desktop started.
    • isTransientBearerBootstrapError (apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts:27-37) retries only fetch errors, timeouts and 502/503/504. The WSL server's invalid_credential is final, so it surfaces as DesktopLocalEnvironmentAuthSessionBootstrapError.

    Proposed fix

    1. Keep binding loopback when network access is off. That already happens in local-only mode. When it's on, consider binding 127.0.0.1 as well, or otherwise making sure another process can't shadow loopback.
    2. After readiness, check that the backend is the one we launched, for example by comparing the environment id or PID from /.well-known/t3/environment. On a mismatch, restart on the next free port.
    3. Show a clearer error when the bearer bootstrap is rejected ("another T3 Code server is answering on port N") instead of the generic crash.

    Related: #9056, #12919, #7702, #10360.

    Workaround
    Stop the WSL t3code.service, or move it to a different port. Then relaunch the desktop app. A second launch also works, because the scan skips the port the relay holds.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 7, 2026
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.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions