[Bug]: On macOS, runtime prune never deletes a version whose directory was on the service PATH#25399

opened9d agobyAaur3l14no

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. On macOS, install the background service with t3 service install on version A. The LaunchAgent's PATH includes ~/.t3/runtime/versions/A.
  2. Start the service. Its startup prune calls shellLauncherTargets, which looks for t3 in every PATH directory, finds ~/.t3/runtime/versions/A/t3, and appends that path to ~/.t3/runtime/launchers.
  3. More than an hour after A was installed, update to version B with t3 update.
  4. Restart the service and list ~/.t3/runtime/versions.

Expected behavior

A is deleted: it is not active, not running, and no t3 launcher points at it.

Actual behavior

A stays. ~/.t3/runtime/launchers lists ~/.t3/runtime/versions/A/t3, which resolves into A, so selectUnusedRuntimes counts A as referenced. That file is only ever appended to, so A stays pinned even after its directory leaves PATH. Every version whose directory reaches the service's PATH is kept for good, about 270 MB each.

Seen on two Macs going from 0.1.0 to 0.1.2. On one of them, the startup prune after the service was reinstalled on 0.1.2 logged Removed unused t3 runtimes. { versions: [ '0.1.1' ] }. It removed 0.1.1, which only ever ran through client-started server updates and never reached the plist, and kept 0.1.0, whose t3 was in runtime/launchers. Both Macs now have versions/0.1.2/t3 recorded, so 0.1.2 will be kept after the next update too.

Our Linux hosts are not affected: the systemd unit T3 writes does not set PATH, and their runtime/launchers holds only ~/.local/bin/t3.

Cause on main @ 1ecb1a0f9346c7902e98d4362cee0ececd93962c:

  • bootService.ts#L547-L558: the LaunchAgent's PATH includes path.dirname(host.execPath), a runtime version directory. #17532 reports the same entry from another side: a plain t3 in the service resolves to an old build.
  • pinnedRuntime.ts#L406-L409: shellLauncherTargets checks every PATH directory for a t3, so it finds the runtime's own entry binary.
  • pinnedRuntime.ts#L418-L445: any t3 whose real path is inside the versions directory is appended to runtime/launchers. Nothing removes entries.
  • pinnedRuntime.ts#L561 and #L575-L576: prune keeps any version a recorded launcher resolves into.

Possible fixes, for maintainers to choose: have shellLauncherTargets skip a t3 that is itself a runtime's entry binary (directly inside versions/<version>/), both on PATH and in the recorded file, which also unpins existing installs; and keep runtime directories off the service's PATH (#17532).

Impact

Minor bug or occasional failure

Version or commit

0.1.2; the code above is unchanged on main @ 1ecb1a0

Environment

macOS 26.6 and 26.2 (arm64), launchd user service

Logs or stack traces

shell
1$ cat ~/.t3/runtime/launchers
2~/.local/bin/t3
3~/.t3/runtime/versions/0.1.2/t3
4$ /usr/libexec/PlistBuddy -c "Print :EnvironmentVariables:PATH" ~/Library/LaunchAgents/com.t3tools.t3code.service.plist | tr : '\n' | grep runtime/versions
5~/.t3/runtime/versions/0.1.2

Workaround

After each update, delete the previous version's directory and its line in ~/.t3/runtime/launchers. Keep the version the LaunchAgent's ProgramArguments points at (#17490).