Note
Grok responding on behalf of Julius.
Thanks for the detailed report. I checked it against main @ 83a82a4, and the core of it holds up.
Confirmed in code
- Every remote SSH script hardcodes
$HOME/.t3and never readsT3CODE_HOMEor the installed service's home:- Discovery:
DEFAULT_SERVER_HOME="$HOME/.t3"andDEFAULT_RUNTIME_FILE=…/userdata/server-runtime.json(tunnel.ts#L545-L547). - Launch:
serve … --base-dir "$DEFAULT_SERVER_HOME"(L702). - Pairing:
auth pairing create --base-dir "$HOME/.t3"(L723-L734). - Archive runtime cache (L449-L458) and launcher state for the stop/log scripts (L737, L760).
- Discovery:
--base-dirtakes precedence overT3CODE_HOMEforserve, which falls back to~/.t3only when neither is set (cli/config.ts#L319-L327, os-jank.ts#L105-L111). So exportingT3CODE_HOMEon the host can't redirect the launched server.- Both the runtime file and the environment ID live under
<home>/userdata/(config.ts#L164-L165). A server started on a fresh~/.t3therefore can't see the service's runtime file and comes up with its own environment ID. - Launch runs as
sh -l -s(L878) and pairing as a non-loginsh -s(L939), so aT3CODE_HOMEset only in a login profile wouldn't reach pairing anyway. - The "a different machine" check runs only after
ensureSshEnvironmenthas launched the server and issued a pairing token (web/connection/platform.ts#L190-L214). That error path doesn't callssh.disconnect, so nothing tears down the tunnel or the managed server at that point. I didn't trace whether something stops it later. For example, the desktop tunnel manager's finalizer does run the stop script for tunnels still open when it shuts down. - The service's home can be read without asking the user. The systemd unit at
~/.config/systemd/user/t3code.serviceand the plist at~/Library/LaunchAgents/com.t3tools.t3code.service.plistboth carryT3CODE_HOME(bootService.ts#L108, L241-L247, L309-L314), andbootServiceBaseDirOfalready parses it back out (L66). There's noservice.envonmain. That file only exists in open #12633, under$T3CODE_HOME, so it wouldn't help the launcher find the home.
One small correction: the cli/app.ts#L200 link in the report is the t3 app command. The serve precedence is the cli/config.ts range above. It behaves the same way.
Root cause. The SSH launcher assumes the remote home is ~/.t3. When the service uses a different home, discovery misses it, and a second managed server starts on ~/.t3 with a new environment ID. Adding a route to the existing environment then fails the ID check, and that server is left running.
Possible fixes (untested)
- Resolve the remote home once per connection and use it everywhere: discovery,
serve --base-dir, pairing, the runtime cache and probably launcher state. The order would beT3CODE_HOMEif set, thenT3CODE_HOMEfrom the installed unit or plist (the formatbootServiceBaseDirOfreads), then$HOME/.t3. Pairing should get the same resolved value, either passed in or stored in the state dir, so it doesn't depend on which shell type sees the env. - When the environment ID doesn't match, call
ssh.disconnectbefore returning the error. The stop script already skipsexternalservers (L736-L755). Separately, the wording could hint that the host may be using a different T3 home rather than being a different machine.
Workaround. Use ln -s ~/.config/t3 ~/.t3, as you found. Stop any server the launcher already started under a real ~/.t3 before you swap the directory. On shutdown, a server clears <home>/userdata/server-runtime.json (server.ts#L748), and through the symlink that file belongs to the service. Open #9045 targets that.
Related
- #15986, with open PRs #13521 and #13341: the same discovery block. A fix here will likely conflict with those.
- #5749: the launcher replaces an external service.
- #7921 (closed, not merged): it resolved the service home via
systemctl --user showplus/proc/<pid>/environ. That piece didn't land with #15467. - #10810: XDG paths.
- #14115: servers sharing one base dir, the opposite failure mode.
I found no open PR that makes the SSH launcher honor a custom home.