pingdotgg / pingdotgg/t3code

[Bug]: systemd user unit from `t3 service install` sets no PATH, so providers in ~/.local/bin are invisible to the service

Open
#4,913 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

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

Not a duplicate, but same family as #4697 ("No installed editors found") and #4846 (Windows desktop resolver ignores User PATH). Those two plus this one all reduce to: the server resolves external commands against whatever PATH it inherited, and that PATH is wrong whenever it wasn't launched from an interactive login shell.

Area

apps/server

Steps to reproduce

On a headless Ubuntu box, with providers installed the ordinary way so their launchers land in ~/.local/bin:

  1. command -v claude codex/home/caesar/.local/bin/claude, /home/caesar/.local/bin/codex
  2. npm i -g t3 (npm prefix is ~/.local/npm-global, no sudo needed)
  3. t3 service install
  4. systemctl --user start t3code.service
  5. Open the web UI and try to start a thread.
Expected behavior

The service resolves claude / codex exactly as t3 serve does when run by hand from a login shell.

Actual behavior

Providers are not found. The server itself is healthy — it listens, pairs, authenticates, and the web client works — but there are no usable providers, so no thread can run.

The cause is that the generated unit sets no PATH, so the service inherits the systemd user manager's minimal environment:

  • Service PATH (what t3 gets): /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
  • Login shell PATH (what a human gets): /home/caesar/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

~/.local/bin is prepended by Ubuntu's default ~/.profile, which only runs for login shells. A systemd user unit never sources it. So the one directory where user-scoped CLI tools normally live is exactly the one the service cannot see.

This also makes the failure confusing to diagnose: running t3 serve manually over SSH works if you have a login shell, so the provider list looks fine when you test by hand and empty when the service runs.

Impact

Major degradation or frequent failure

The headless Linux path is the "self-hosted server" use case, and on a fresh install it silently produces a server with zero providers.

Version or commit

t3@0.0.31

Environment

Ubuntu 26.04 LTS, kernel 7.0.0-28-generic, x86-64 · node v22.22.1 at /usr/bin/node · npm prefix /home/caesar/.local/npm-global · systemd user service with Linger=yes · no desktop session, headless, reached over Tailscale.

Logs or stack traces
# unit generated by `t3 service install` (verbatim) - note the absence of any PATH
[Unit]
Description=T3 Code server
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
WorkingDirectory=%h
Environment=T3CODE_HOME=/home/caesar/.t3
Environment=T3_BOOT_SERVICE_UNIT=t3code.service
ExecStart=/usr/bin/node /home/caesar/.local/npm-global/bin/t3 serve
Restart=always
RestartSec=5
StandardOutput=append:/home/caesar/.t3/userdata/logs/boot-service.log
StandardError=append:/home/caesar/.t3/userdata/logs/boot-service.log

[Install]
WantedBy=default.target

# what the running service actually has, before the workaround
$ tr '\0' '\n' < /proc/$(systemctl --user show t3code.service -p MainPID --value)/environ | grep ^PATH=
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

# where the providers actually are
$ bash -lc 'command -v claude codex'
/home/caesar/.local/bin/claude
/home/caesar/.local/bin/codex
Workaround

A systemd drop-in at ~/.config/systemd/user/t3code.service.d/override.conf, which also survives t3 service update (editing the generated unit does not):

[Service]
Environment=PATH=/home/caesar/.local/bin:/home/caesar/.local/npm-global/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

After systemctl --user daemon-reload && systemctl --user restart t3code.service, providers resolve and threads run normally. Note that systemctl --user enable --now does not restart an already-active unit, so a plain enable --now after editing the drop-in silently keeps the old environment.

Suggested fix

Have t3 service install emit an explicit Environment=PATH= in the unit, captured from the installing user's login PATH (bash -lc 'echo $PATH') rather than from the environment the installer happens to run in. At minimum, prepend ~/.local/bin and the npm global bin directory.

Doing it at install time also fixes #4697-style reports on Linux, and is the same underlying defect as #4846 on Windows.

Related papercut (secondary, happy to split out)

t3 service install exposes no --host / --port, and the unit it writes binds to loopback. On a headless box that means the freshly installed service is unreachable from anywhere until you discover that T3CODE_HOST / T3CODE_PORT work as environment variables. I verified both do, via the same drop-in:

Environment=T3CODE_HOST=100.x.x.x
Environment=T3CODE_PORT=3773

Either exposing flags on service install or documenting those variables in the headless setup path would save the guesswork.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin at the t3 service install entry point in apps/server and trace where the systemd user unit shown in the report is generated. Check how the installer obtains the user's PATH, then verify that the generated unit includes the needed user and npm-bin paths and that providers resolve after a daemon reload and restart.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, node.js, typescript
Domain
cli, devops, infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.