[Bug]: Desktop update can't relaunch when an SSH client starts a server during the restart#22996

opened2d agobyAajbarryiii

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. On host A, run T3 Code desktop with an update downloaded and ready to install.
  2. On machine B, connect T3 Code to host A over SSH and leave it connected.
  3. On host A, install the update.
  4. While A's desktop is stopped for the update, B loses its tunnel and reconnects. Its launch script finds no server on A and starts t3 serve against ~/.t3.
  5. A's updated desktop relaunches.

This depends on timing. B has to reconnect during the gap, which was about 8 seconds here.

Expected behavior

The updated desktop relaunches and owns its home. B waits through the update and then reconnects to the desktop's new server.

Actual behavior

The SSH-launched server takes the home's ownership lock first. The relaunched desktop shows "This T3 home is unavailable" and quits. The only way out is to find and stop the SSH-launched server by hand.

The old backend did stop cleanly (1.95 s, well under the 5 s stop timeout), so this is not the slow-shutdown race that the open PRs #22572 and #22345 address.

Impact

Blocks work completely

Version or commit

Updating 0.1.0 → 0.1.1 on the host. The SSH-launched server was 0.1.0.

Environment

Host: macOS 27.0, Apple Silicon, desktop app, no background service. Remote client: T3 Code desktop 0.1.0 on NixOS 26.05 (Linux 6.18), connected over SSH via Tailscale.

Logs or stack traces

shell
1Host-local times (UTC-7). host = host's desktop/server traces and ~/.t3/ssh-launch state; client = remote client's own traces.
2
37:49:51.9 client EnvironmentSupervisor.connectionLost for the SSH environment
47:49:54 host updater finishes stopping the desktop backend (1.95 s)
57:49:56.8 client reconnect: readiness on the old tunnel fails, then
6 ssh/tunnel.launchOrReuseRemoteServer runs the launch script (8.4 s)
77:50:01 host new server.log under ~/.t3/ssh-launch/<key>/;
8 a 0.1.0 `t3 serve --base-dir ~/.t3` starts
97:50:02 host updated desktop backend refused: home already owned (onStateDirOwned)
107:50:04 host SSH-launched server reports ready (headless)
117:50:05.3 client launch script returns; tunnel to the new server opens

Screenshots, recordings, or supporting files

No response

Workaround

Stop the SSH-launched t3 serve process, then reopen T3 Code.

Relation to #21423

#21423 describes the same lock conflict after a normal quit or reboot. It was closed because the ownership lock is working as designed, and automatically attaching the desktop to an existing server is a feature request (#20250, and #8115 explains why that is a large design). I agree with both points. This report is narrower. During a desktop update, the desktop is guaranteed to come straight back, and the SSH launcher starts a competing server in that short window anyway.

Proposed fix (small, no lock or protocol changes)

  1. Updater: before stopping backends in installDownloadedUpdate, write a short-lived marker, runtime/desktop-update-in-progress, next to the existing desktop-update-restart marker. Remove it wherever that marker is removed (resetInstallAction, recoverFailedInstall). It is a separate file because desktop-update-restart is consumed during shutdown for the T3 Connect tunnel handoff.
  2. SSH launch script: at the point where it would start a new t3 serve because no server was found, check the marker. If it is under 1 minute old, exit with "T3 Code on this host is installing an update; the connection will retry shortly" and start nothing. The 1-minute limit matches DESKTOP_UPDATE_RESTART_MARKER_TTL.

Nothing else changes:

  • The client's connection supervisor already backs off and retries after a failed launch.
  • On a later retry, the launcher's existing branch for adopting a running default-home server picks up the relaunched desktop.
  • The ownership lock, normal quit, crash and reboot behavior, and the client protocol and UI are all untouched.
  • No process is stopped, and the marker expires by itself if the desktop never comes back.

Limitation: only clients running a version with the updated launch script are protected, because the script and the t3 it runs come from the client. Older clients keep today's behavior until they update.

Implementation ready, no PR opened

CONTRIBUTING.md asks that a substantial bug fix link an issue maintainers have triaged, so I haven't opened a PR. The fix is ready on a fork branch, with focused contract tests, in case it's useful: https://github.com/pingdotgg/t3code/compare/main...ajbarryiii:t3code:fix/defer-ssh-launch-during-desktop-update (one commit on current main, +216/−31 across 6 files, mostly tests). It was written by Claude Opus 5.5 in Claude Code and reviewed by GPT-6.1 Sol in Codex.

If this scope is acceptable, I'm happy to open it as a PR.

Synced from GitHub

I saw #23005 got opened for this. Thanks for addressing it so quickly!

On the request for logs: here are the host and client logs from the original occurrence, covering the restart gap: https://gist.github.com/ajbarryiii/7546bdde3a0feb81d4897b1b8ac90488. In short, the old backend stopped cleanly at 07:49:54.5. The remote client started its launch script at 07:49:56.8, and the SSH-launched server took the home before 07:50:02.9. The updated desktop was then refused with ServerAlreadyRunningError on that launch and on every relaunch until I stopped that server by hand.

It's hard to replicate on demand. An update only goes one way, so each attempt needs a new release to install. The remote client also has to reconnect inside the ~8 s restart gap. I'll try to replicate once #23005 lands in a nightly, with both machines on that nightly, and report back.

Synced from GitHub

Same failure here on 0.1.1 → 0.1.2. Host is macOS on Apple Silicon (desktop app, no background service). The client is T3 Code desktop 0.1.1 on Linux, connected over SSH via Tailscale.

Host-local times:

text
119:16:50 desktop: updates.install writes runtime/desktop-update-restart, backendInstance.stop (3.7 s)
219:16:59 SSH-launched 0.1.1 `t3 serve --host 127.0.0.1 --port 3773 --base-dir ~/.t3` starts (PPID 1, SSH_CLIENT = the Linux client)
319:16:59 updated 0.1.2 desktop: backend start -> ServerAlreadyRunningError, code=78
419:17:26, 19:19:44, ... every relaunch refused the same way

The gap was about 9 s. As in the original report, the only way out was to stop the SSH-launched server by hand. The client then relaunched another one within seconds, so I had to reopen the desktop immediately to win the race. This happens on every update for me, so I'd love to see #23005 land.