Skip to content

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

Open

Description

@ajbarryiii

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

Host-local times (UTC-7). host = host's desktop/server traces and ~/.t3/ssh-launch state; client = remote client's own traces.

7:49:51.9  client  EnvironmentSupervisor.connectionLost for the SSH environment
7:49:54    host    updater finishes stopping the desktop backend (1.95 s)
7:49:56.8  client  reconnect: readiness on the old tunnel fails, then
                   ssh/tunnel.launchOrReuseRemoteServer runs the launch script (8.4 s)
7:50:01    host    new server.log under ~/.t3/ssh-launch/<key>/;
                   a 0.1.0 `t3 serve --base-dir ~/.t3` starts
7:50:02    host    updated desktop backend refused: home already owned (onStateDirOwned)
7:50:04    host    SSH-launched server reports ready (headless)
7: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: 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.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 21, 2026
  2. ajbarryiii commented on Oct 21, 2026

    @ajbarryiii
    Author

    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.

  3. kraveir0 commented on Oct 23, 2026

    @kraveir0

    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:

    19:16:50  desktop: updates.install writes runtime/desktop-update-restart, backendInstance.stop (3.7 s)
    19: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)
    19:16:59  updated 0.1.2 desktop: backend start -> ServerAlreadyRunningError, code=78
    19: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.

  4. ajbarryiii commented on Oct 27, 2026

    @ajbarryiii
    Author

    Note

    🤖 Claude Opus 5.5 responding on behalf of AJ (@ajbarryiii)

    I opened #26191, which builds on #23005. It adds the fix for that PR's open audit finding and end-to-end evidence. The test used packaged AppImages in a VM, with a client connected over real SSH while the host installed a real update. With the host's restart slowed so the backend was gone for several seconds:

    • main: the updated desktop was locked out of its home in 6 of 8 attempts.
    • The fix: under the same conditions, the client waited and attached to the updated desktop in 8 of 8 attempts.

    Details: https://gist.github.com/ajbarryiii/045721f97d50f99069a67882c4dbd0eb

  5. Tarekkharsa commented on Oct 31, 2026

    @Tarekkharsa

    Note

    Posted by Claude Code (Opus 5.5) on behalf of the reporter.

    Another occurrence, on a current nightly: updating 0.1.6-nightly.20261031.3969 to .3977. The host is a Mac on Apple Silicon running the desktop app with no background service, and the client is T3 Code desktop on a second Mac, connected over SSH via Tailscale.

    # Host local time, 2026-10-31
    17:03:43  desktop: updates.install, backendInstance.stop (5.0 s)
    17:03:48  ElectronUpdater.quitAndInstall
    17:03:52  SSH-launched `t3 serve --host 127.0.0.1 --port 3773 --base-dir ~/.t3` starts (PPID 1, SSH_CLIENT = the other Mac)
    17:03:53  updated desktop: backendInstance.start; SSH-launched server listening on 3773
    17:03:54  updated desktop backend on port 3774: ServerAlreadyRunningError, code=78; error dialog, then the app quits
    

    About 9 s from the start of the stop to the refusal, which matches the earlier reports here. Stopping the SSH-launched server and reopening the desktop straight away cleared it, and the client then attached to the desktop's server.

    On this host the race was also harder to recover from, because the SSH-launched server didn't exit on SIGTERM. Details and the stack are in #22983, which covers the separate problem of a server hanging in process.exit() while holding the owner lock.

  6. gwadson commented on Nov 3, 2026

    @gwadson

    Note

    Posted by Claude Code (Opus 5.5) on behalf of the reporter.

    Same lockout without any update, so the desktop-update-in-progress marker from #23005 / #26191 doesn't cover it.

    Host: Ubuntu laptop, desktop 0.1.6 (.deb), no background service. Client: T3 Code desktop on a Mac, connected over SSH via Tailscale.

    # Host local time
    11-02 18:28     host reboots; the desktop app is not started
    11-02 18:33     Mac reconnects; the launch script finds no server and starts a managed
                    `t3 serve --host 127.0.0.1 --port 3773 --base-dir ~/.t3` (0.1.6, PPID 1, SSH_CLIENT = the Mac)
    11-03 09:17:50  desktop opened on the host; its backend on port 3774 exits 78:
                    ServerAlreadyRunningError: A T3 Code server already owns ~/.t3/userdata
    11-03 09:18:00  error dialog closed, app quits
    

    The SSH-launched server had no agent children and stopped promptly on SIGTERM. Reopening the desktop straight away worked.

    The update marker can't help here: nothing is in progress, so there's nothing to wait for. Any time the client connects while the host's desktop isn't running (after a reboot, before login, after quitting the app), it starts its own server, and the desktop is locked out whenever it's opened later, possibly hours afterwards. Autostarting the desktop at login narrows this, but a host sitting at the login screen still loses.

    Possible directions: when the owner is a managed SSH-launched server (ssh-launch/<key>/managed) with no running turns, the desktop could offer to stop it and take over the home instead of quitting; or the managed server could release the home when a local desktop asks for it. The dialog wording for this case is tracked in #22820.

  7. ajbarryiii commented on Nov 3, 2026

    @ajbarryiii
    Author

    @gwadson the marker in #26191 only helps when the desktop is about to come back, like during an update. After a reboot or a quit there's nothing to wait for, so that case needs the desktop to take over from an idle SSH-launched server when it starts.

    The two fixes would work together, since the marker still keeps an update from spinning up a server that gets killed seconds later. A takeover has open questions, though: what happens to the other machine's running turns, and what the dialog says (#22820). So I think it deserves its own issue. Happy to open one and work on it once a maintainer weighs in.

  8. AliAlbarrak commented on Nov 4, 2026

    @AliAlbarrak

    Note

    Posted by Claude Code (Opus 5.5) on behalf of the reporter, from a t3 triage session.

    Same lockout without an update, on 0.1.6. This adds one detail: while the SSH client stays connected, the managed server comes back within about 30 s of being killed, so stopping it by hand only helps if the desktop is opened straight away.

    Host: work Mac Studio, Apple Silicon, macOS 27.0, desktop app T3 Code (Beta) 0.1.6, no background service (t3 service status: not installed). Client: T3 Code desktop on a personal PC with this host saved as an SSH environment, connected through a self-hosted Teleport SSH proxy (not Tailscale). The host has no network route back to the client. ~/.t3/ssh-launch/<id>/managed was present, and the launched server ran the 0.1.7 runtime.

    # Host local time (UTC+3), 2026-11-04
    16:07:12  managed `t3 serve --host 127.0.0.1 --port 3773 --base-dir ~/.t3` (0.1.7, PPID 1) listening, T3 Connect tunnel up
    16:09:02  desktop 0.1.6 backend on 3774 -> ServerAlreadyRunningError, code=78
    16:11:18  same, on relaunch
    16:12:49  same, on relaunch
    16:13:xx  SIGTERM to the managed server
    16:13:26  managed server is listening again (new PID, PPID 1). The desktop app was not running.
    16:16:00  desktop relaunch -> ServerAlreadyRunningError, code=78
    16:16:xx  SIGTERM again
    16:16:59  managed server is listening again (new PID, PPID 1)
    

    Workaround that worked: kill the managed server and open the desktop app straight away, before the client relaunches it. After that, updating the desktop to 0.1.7 and quitting and reopening it both worked, because the desktop owned the home and the client attached to it.

    The dialog still pointed at t3 service uninstall, which doesn't apply here (#22820). #30580, which makes the desktop attach to a running local server, looks like the fix for this case.

  9. fe-franco commented on Nov 5, 2026

    @fe-franco
    Contributor

    Same lockout on T3 Code Nightly 0.1.8-nightly.20261105.4218, after a plain quit, not an update. Host: MacBook Pro, Apple Silicon, Darwin 25.6.0, desktop app, Local environment on, no background service. Client: T3 Code Nightly desktop on a MacBook Air with the host saved as an SSH environment, connected over Tailscale.

    Timeline (host local time, UTC-3):

    15:13:52  last desktop trace event; the user quits T3 Code on the host
    15:14:02  client relaunches over SSH: ~/.t3/runtime/versions/0.1.8-nightly.20261105.4218/t3 serve
              --host 127.0.0.1 --port 3773 --base-dir ~/.t3
              (PPID 1, SSH_CLIENT = client's Tailscale IP, ~/.t3/ssh-launch/<key>/managed)
    15:14:36  host desktop relaunch picks :3774, backend exits 78 ServerAlreadyRunningError,
              "This T3 home is unavailable" (again at 15:14:48 and 15:15:41)
    ~15:17    SSH-launched server stopped with SIGTERM; lock released
    15:18:16  client starts a new managed server over SSH, same command, takes the lock again
    15:19:19  host desktop relaunch refused again, exit 78
    

    The way out: quit the desktop app on the client. That also stopped the SSH-launched server on the host (see #27183). The lock was free afterwards and the host desktop could start.

    The dialog says "such as the background service", which sent us looking for a launchd service that didn't exist. Pointing at ~/.t3/ssh-launch/*/managed and the client's address would have saved the search (#22820).

  10. gwadson commented on Nov 6, 2026

    @gwadson

    @ajbarryiii thanks, #30580 (attach to a running local server) looks like it covers our case, so I'll follow that.

  11. CopyJosh commented on Nov 8, 2026

    @CopyJosh

    Note

    🤖 Claude Opus 5.5 responding on behalf of CopyJosh

    Same lockout on T3 Code Nightly 0.1.8-nightly.20261107.4340, with one new detail: the SSH-launched server ran an older version than the host app, and it logged a divergent migration history against the host's database.

    Host: Mac, Apple Silicon, macOS 27, T3 Code (Alpha) Nightly 0.1.8-nightly.20261107.4340, no background service (t3 service status: not installed). Client: an Arch Linux machine, connected over Tailscale SSH. The Mac also has the Linux machine saved as an SSH environment, so each machine launches servers on the other.

    Timeline (host local time):

    19:54:44  host app not running; SSH from the Linux machine starts
              ~/.t3/runtime/versions/0.1.8-nightly.20261106.4247/t3 serve
              --host 127.0.0.1 --port 3773 --base-dir ~/.t3
              (PPID 1, ~/.t3/ssh-launch/<key>/managed, run-t3.sh pins
              T3_ARCHIVE_VERSION='0.1.8-nightly.20261106.4247')
    ~19:56    opening the 20261107 app shows "Another T3 Code server ... is using
              this data directory"; the SSH-launched server is killed by hand
    19:57:16  a new 20261106 server starts through the same ssh-launch key
              (run-t3.sh rewritten at launch, same pin)
    ~19:59    SSH session closes, the managed server exits; the 20261107 app
              starts and owns ~/.t3 (server-runtime.json serverVersion 20261107.4340)
    

    The ssh-launch/<key>/server.log on the host shows 4 such launches in total, not only today's.

    The version mismatch: the 20261106 server logged this against the host's statev2.sqlite, which the newer app had migrated:

    WARN: Database migration history diverges from this build; recorded migration ids are skipped, not reconciled by name.
      divergent: [
        '77:RepairBooleanSettledOverride (unknown to this build)',
        '78:DropOrchestrationEventCorrelationIndex (unknown to this build)',
        '79:DelegatedRecoveryIndexes (unknown to this build)'
      ]
    

    So the launch script can start an older server on a home whose schema is newer than that server knows. That is a data risk in addition to the lockout. #30580 fixes the lockout by attaching, but the app would then attach to that older server.

    Not verified: which process on the Linux machine sent the 20261106 pin. Its own server had run 20261107.4340 since 19:55, and a few minutes later 20261107.4340 was its only installed runtime. The connection catalogs are encrypted, so I could not read the environment entries.

    Workaround: the same as above. Stop the SSH-launched t3 serve, then open the app before the client reconnects.

  12. deleted a comment from Wladefant on Nov 8, 2026
  13. garrickgan commented on Nov 9, 2026

    @garrickgan

    Note

    Posted with help from Claude Code on behalf of the account owner.

    Same lockout on stable 0.1.7 (not Nightly), after the macOS desktop auto-updated 0.1.6 → 0.1.7. A few details that may help:

    Setup: Host is a MacBook Air (Apple Silicon, macOS 27), desktop app, no background service. Client is a Linux laptop with the T3 Code .deb (0.1.6 at the time), with the Mac saved as an SSH environment over Tailscale.

    What we saw:

    1. During the update restart, the client's launch script started a 0.1.6 t3 serve --base-dir ~/.t3 on the host. It logged Database migration history diverges from this build (migrations 77–79 unknown), because the 0.1.7 desktop had already migrated the database.
    2. The relaunched desktop failed with ServerAlreadyRunningError (exit 78).
    3. Killing the SSH-launched server didn't help, because the client relaunched it within about 1 second, every time. Updating the client to 0.1.7 only changed the version of the server it relaunched.
    4. Following the dialog's advice, we tried t3 service install. The desktop app then failed the same way against the service, so we uninstalled it. While swapping it out, the reconnected client grabbed the home again.
    5. The only reliable recovery was to disconnect the environment on the client, kill the server, relaunch the desktop, then reconnect. After that, ~/.t3/ssh-launch/<key>/managed reads external and the client attaches to the desktop's server correctly.

    Side effect: each kill cancelled whatever run was in progress on the host (terminalizedRuns: 1 in shutdown reconciliation).

    +1 for #30580. Separately, the dialog suggesting the background service is misleading for desktop users (related: #22820).

  14. Jaimendo99 commented on Nov 10, 2026

    @Jaimendo99

    Same lockout on T3 Code Nightly, without an update. One detail differs from the earlier reports: here the SSH-launched server is newer than the host's desktop app.

    Setup: The host is a Mac mini (Apple Silicon, macOS 27.0) running the desktop app T3 Code (Nightly) 0.1.8-nightly.20261109.4429, with no background service. The client is T3 Code desktop on a MacBook, with the mini saved as an SSH environment. The client's exact version is unknown, but the runtime it installed on the host is 0.1.9-nightly.20261110.4452.

    Timeline (host local time, UTC-5):

    23:43:56  client's launch script starts ~/.t3/runtime/versions/0.1.9-nightly.20261110.4452/t3 serve
              --host 127.0.0.1 --port 3773 --base-dir ~/.t3
              (PPID 1, ~/.t3/ssh-launch/<key>/managed present; sshd-session holds a tunnel to :3773)
    23:45:56  host desktop (0.1.8-nightly) backend on :3774 exits 78:
              ServerAlreadyRunningError: A T3 Code server already owns ~/.t3/userdata
    23:46:03  same again on relaunch
    

    The host's backend log shows the same refusal earlier that day, at 14:36 and 18:00, so this keeps happening on this host.

    Why the version direction matters: Earlier reports described an older SSH-launched server meeting a newer database. Here the client installed a newer runtime on the host than the host's own app. That server now owns the database, at migration 80 according to /.well-known/t3/environment. Once the user stops it, the older host desktop may hit the reverse migration problem. So attaching to the running server (#30580) or taking it over probably also needs a version check. Otherwise the host ends up stuck on the older app.

    The dialog also tells the user to deal with an "older background service", but no service is installed. That's the problem described in #22820.

    Note

    Posted by Claude Code (Claude Opus 5.5) on behalf of the reporter, from a t3 triage session.

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