t3 service install silently rebinds an existing service to a different base dir, orphaning its data#23858
What happened
I was working with t3 in headless mode for weeks, connecting to this machine from my other laptop, tablet, phone. Today I decided to install desktop app on this machine. Once I did connect to the service I noticed the pop up saying the t3 server can be updated so I clicked update. All was fine and dandy but I noticed I can't open my files anymore. So I went to terminal to check the service status which said service is corrupted and can be fixed with
t3 service install. Once I installed the service all my sessions and projects disappeared.
Diagnosis
The user's long-lived service was installed with --base-dir /mnt/workspace/Projects, so the unit's T3CODE_HOME pointed there and all state (685 threads, 6 projects, 704MB statev2.sqlite) lived under that base dir. The default ~/.t3 held only leftovers from first-day experiments. The server's self-update (0.0.46-nightly.20261005.2689 → 0.1.2-nightly.20261022.3526) completed fine.
The data "disappeared" because of two compounding behaviors when a t3 CLI is run without --base-dir against a unit installed with one:
t3 service statusreports a healthy foreign-base-dir unit as needing repair. Inapps/server/src/cloud/bootService.ts,statuscompares the on-disk unit againstdetectedManager.render(plan)whereplanis rendered for the CLI's owninput.baseDir(default~/.t3). A unit installed for a different base dir can never match, socurrentis false and the CLI prints "needs an update or repair / Next: Runt3 service installto repair it."statuseven computesinstalledBaseDirfrom the unit, but neither uses it in thecurrentcomparison nor surfaces it informatServiceStatus.t3 service installoverwrites the unit without comparing base dirs, and its downgrade guard reads the wrong state file.installreadspreviousStateTextfromstatePathunder the CLI'sinput.baseDir(~/.t3/runtime/service-state.json, which said0.0.46-nightly.20261005.2702— equal to the CLI, so no refusal) instead of the unit's actual base dir (/mnt/workspace/Projects/runtime/service-state.json, which said0.1.2-nightly.20261022.3526). It then stops the running service and rewrites the unit for~/.t3. The real instance is silently replaced by a server bound to an empty state dir — to the user, total data loss.
Notably, restart and uninstall both already guard on this exact condition (bootServiceBaseDirOf(unit) vs path.resolve(input.baseDir)) and refuse to touch a foreign unit. install — the only destructive one of the three — has no such guard. Verified at tag v0.0.46-nightly.20261005.2702 (cfa4f765) and still present on main as of 2026-10-22.
Suggested direction: give install the same installedBaseDir comparison and refuse (or require an explicit flag) when it differs; read the downgrade-guard state from the unit's base dir; and print installedBaseDir in t3 service status so the mismatch is visible instead of being reported as corruption.
Steps to reproduce
t3 service install --base-dir=/some/other/dirand use the server (create a thread).- Install a second
t3CLI (e.g. a distro package) and runt3 service statuswith no flags → reports "needs an update or repair" although the service is healthy. - Run
t3 service installwith no flags, as the status output suggests. - The unit is rewritten for the default
~/.t3; the original instance stops and the service now serves an empty state dir. All sessions/projects appear gone. No downgrade or mismatch warning is shown even when the replaced service was newer than the CLI.
Version
Service that was clobbered: 0.1.2-nightly.20261022.3526 (self-updated from 0.0.46-nightly.20261005.2689). CLI that clobbered it: 0.0.46-nightly.20261005.2702 (AUR t3code-nightly-bin). Missing guard confirmed on main.
Environment
Linux x64 (CachyOS, kernel 7.2.9-2), Node v26.8.2, systemd user service.
Evidence
| 1 | # Unit after `t3 service install` (no --base-dir) — rebound to default home: |
| 2 | Environment=T3CODE_HOME=~/.t3 |
| 3 | ExecStart=~/.t3/runtime/versions/0.0.46-nightly.20261005.2702/t3 __service-launcher |
| 4 | |
| 5 | # Real instance, cleanly shut down at the moment of the reinstall: |
| 6 | # /mnt/workspace/Projects/userdata/logs/boot-service.log |
| 7 | [21:22:41.924] INFO: Server update prepared; handing off to the service launcher. |
| 8 | [21:22:47.756] INFO: Listening on http://127.0.0.1:3773 |
| 9 | [22:14:57.696] INFO: V2 orchestration shutdown reconciliation completed |
| 10 | |
| 11 | # /mnt/workspace/Projects/runtime/service-state.json (ignored by the downgrade guard): |
| 12 | { "protocol": 3, "activeVersion": "0.1.2-nightly.20261022.3526", |
| 13 | "update": { "fromVersion": "0.0.46-nightly.20261005.2689", |
| 14 | "targetVersion": "0.1.2-nightly.20261022.3526", "status": "committed" } } |
| 15 | |
| 16 | # Replacement server booting on the stale default-home state: |
| 17 | # ~/.t3/userdata/logs/boot-service.log |
| 18 | [22:14:59.774] WARN: Database migration history diverges from this build; recorded |
| 19 | migration ids are skipped, not reconciled by name. |
Related issues
#5749 (SSH reconnect can replace an external T3 service with a competing managed runtime) is the same family — one T3 install clobbering another's service ownership — but a different entry point and mechanism; this one is driven by the service status → service install repair suggestion ignoring installedBaseDir.
Fix applied or workaround
Recovered on the user's machine with no data loss: removed the manual systemd override, then ran the pinned runtime's own CLI — /mnt/workspace/Projects/runtime/versions/0.1.2-nightly.20261022.3526/t3 service install --base-dir=/mnt/workspace/Projects. Workaround until fixed: always pass --base-dir to t3 service ... commands when the service was installed with one.
Filed by
Claude Fable 5 (claude-fable-5) via t3 triage (Claude Code)