[Bug]: Quitting the desktop app still stops SSH-launched servers and kills running turns#27183

opened11d agobyHharshit97

Splitting this out of #15608, as Theo asked on Oct 21 ("desktop quit still stops the managed remote server"). It's the same lifecycle as #10934, whose triage ended with "Needs a maintainer stamp on that quit-vs-disconnect contract before implementation." That decision never happened.

What I'm asking for: quit (and window close) only closes the port forward. Disconnect, turning the environment off, and Remove stay the ways to stop a server T3 launched. The fix is then small (see "Where I'd start").

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. Add an SSH environment for a Linux box and let T3 launch the server (Settings → Environments → Add environment → SSH).
  2. Start a turn there that runs for more than a few seconds.
  3. Cmd+Q the desktop app.
  4. Check ~/.t3/ssh-launch/<id>/server.log on the box, or reopen the app and open the thread.

Expected behavior

The remote server keeps running and the turn finishes. Quit closes the port forward, nothing more.

The docs agree: the only documented way to stop a launched server is removing the connection. The #10934 triage said stopping on quit "is stricter than the documented 'remove connection' rule and is what broke long-running remote work."

Actual behavior

About 5 seconds after Cmd+Q, every SSH environment's server shuts down and its running turns are cancelled.

My repro: a Codex turn ran for ~20s, I hit Cmd+Q, and the remote server logged its shutdown 5.8s later with terminalizedRuns: 1. My other two environments went down within a second of it. Reopening the app 3 minutes later restarted all three and posted "Continue where you left off." to the thread. Recovery works, but the agent was dead the whole time, and so was everything else on that server (I also use it from a browser on another machine).

I run agents on a remote box so they keep going when I walk away from the laptop:

  • Closing the lid is fine since #19432.
  • Closing the window is fine on macOS, where the app stays alive. On Windows and Linux it quits the app, so it hits this too.
  • Cmd+Q, which I use to restart the app or free memory, kills the work on every box at once.

To me those all mean "done with the laptop for now." I found out one of them was destructive by losing a turn.

The workaround suggested on #10951, t3 service install, needs systemd or launchd. My box is a container without systemd. Someone else on #10951 said this made remote connections "next to unusable" and that they'd probably switch to Claude Code Desktop for the time being.

Where I'd start

From main at dd14848153:

  • Cause. The tunnel entry finalizer (tunnel.ts L1561-L1609) kills the ssh -N forward, then calls stopRemoteServer if the entry is still in tunnels. On quit, the manager finalizer (L1378-L1386) closes every entry while they're all still in the map.
  • Deliberate stops don't need it. disconnectEnvironment deletes the entry, then calls stopRemoteServer itself (L1756-L1768). Disconnect, turn-off, and Remove all go through it. The stale-forward path also deletes first (L1652-L1654).
  • Updates are still handled. The launch script reuses a running managed server and replaces it when the runner changed (L677-L687).
  • Fix. Drop the remote stop from the entry finalizer and keep the forward kill. #10951 did this with tests (Macroscope approved an earlier revision) before it was closed pending the decision. Its reconnect half has since been fixed on main by #19432, so only the finalizer change is left.
  • Test. The "waits for %s shutdown before reconnecting the same target" case in tunnel.test.ts (L263) already counts stops. Asserting stops === 1 and remoteRunning === true after the inner Effect.provide(layer) block (after L362) should fail on main, because releasing the layer sends a second stop.
  • Conflict. Open PR #14276 keeps "a later normal quit still stops managed servers," so whichever lands second has to match the other.
  • Docs. docs/internals/remote.md (L165-L167) and docs/user/remote-access.md (L202-L204) should say quit leaves the server running.
  • Cost. The server runs until the environment is removed or turned off, and it holds the remote ~/.t3 lock, so running T3 directly on that host means stopping it first (see #24225, #24318). The #10934 triage already covered the orphan worry from #2614: since #9843 the stop is reliable, and one tracked server surviving a quit isn't that leak.

Impact

Major degradation or frequent failure

Version or commit

Desktop T3 Code (Beta) 0.1.5, remote runtime 0.1.5. Same code on main at dd14848153.

Environment

macOS 26.7.1, three SSH environments on Debian 12 (x86_64), Codex CLI 0.166.0.

Logs

shell
1# times relative to Cmd+Q
2T+0.0s desktop desktop.lifecycle.startWaitingForShutdown
3T+4.9s host C V2 orchestration shutdown reconciliation completed terminalizedRuns: 0
4T+5.2s host B V2 orchestration shutdown reconciliation completed terminalizedRuns: 0
5T+5.8s host A V2 orchestration shutdown reconciliation completed terminalizedRuns: 1
6T+5.8s host A provider-runtime.continue queued; run 1 cancelled (turn started ~19s before quit)
7
8# after reopening the app
9T+189.6s host A, B Listening on http://127.0.0.1:3773
10T+189.8s host C Listening on http://127.0.0.1:3773
11T+190.2s host A provider-runtime.continue ran, run 2 started

Workaround

Start the server on the box yourself before the desktop connects. The desktop adopts it as "external" and never stops it:

sh
1nohup env T3CODE_NO_BROWSER=1 ~/.t3/runtime/versions/0.1.5/t3 serve --host 127.0.0.1 --port 3773 --base-dir ~/.t3 >> ~/.t3/manual-serve.log 2>&1 < /dev/null &

The catch: desktop updates no longer update it, and nothing restarts it after a reboot.

Related: #10934 and #15608 (same bug, closed), #10951 (closed fix), #14268 (forward leak on update, a different path), #26948 (a separate launcher bug I filed).