[Bug]: Linux server keeps the service PATH for its whole run when the login-shell PATH read times out at startup#22336

opened22d agobyNnkoynov

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. Linux server under t3 service install (systemd user unit), with opencode (or claude) only in ~/.local/bin, which the login shell adds and the user manager's PATH doesn't. NixOS here, so there is no /bin/bash.
  2. Make the login shell slower than 5 s at the moment the server starts. In production that was load right after a system switch; to reproduce, put [ -e "$HOME/.slow-login" ] && sleep 8 in /etc/profile.local (or ~/.zprofile) and touch ~/.slow-login.
  3. systemctl --user restart t3code.service, then open an OpenCode thread or refresh providers. Removing ~/.slow-login afterwards changes nothing until the next restart.

Expected behavior

A slow login shell at startup doesn't leave the server without the user's PATH for its whole lifetime: the read is retried (after startup settles, or on a provider refresh), or allowed more time, and a failure is visible.

Actual behavior

hydratePosixPath (apps/server/src/os-jank.ts) tries $SHELL -ilc, the passwd shell, then /bin/bash, each through readEnvironmentFromLoginShell with a 5 s execFileSync timeout. The first two are the same shell here, so it is tried once and times out; /bin/bash doesn't exist on NixOS (or any distro without /bin/bash). mergePathEntries then keeps the unit's PATH, and nothing reads the shell again until the process restarts. Every OpenCode spawn fails with spawn opencode ENOENT, the OpenCode provider lists 0 models, and the only sign is two lines in boot-service.log.

On our server this happened after a deploy at 07:54 and lasted until a manual restart at 16:05. The same zsh -ilc takes 0.23 s once the machine is idle.

#10534 is the same 5 s limit in the macOS desktop app (its open fix #22286 is desktop-only), and #4913 is about the unit having no PATH at all; this is the server-side read on Linux.

Impact

Blocks work completely

Version or commit

0.1.1-nightly.20261019.3360 (main 67c0fc0cda has the same code)

Environment

NixOS 26.11, x86_64, T3 as a systemd user service (t3 service install), login shell zsh (and bash in the repro VM), OpenCode 2.0 provider with binaryPath: "opencode".

Logs or stack traces

shell
1# ~/.t3/userdata/logs/boot-service.log, server start under load
2[server] Failed to read PATH from login shell /run/current-system/sw/bin/zsh. spawnSync /run/current-system/sw/bin/zsh ETIMEDOUT
3[server] Failed to read PATH from login shell /bin/bash. spawnSync /bin/bash ENOENT
4# then, for every OpenCode turn and provider refresh until the restart
5 [cause]: Error: spawn opencode ENOENT

Workaround

A unit drop-in with the PATH the agents need, e.g. ~/.config/systemd/user/t3code.service.d/path.conf with Environment="PATH=%h/.local/bin:...". mergePathEntries keeps it behind the login shell's answer, so it only matters when the read fails. An absolute provider binaryPath covers that one provider only.

Possible fixes: retry the read once after startup (or on server.refreshProviders) while the result is still the inherited PATH; a longer first timeout; and /usr/bin/env bash or sh -l instead of a hard-coded /bin/bash fallback.

Found and written up by Claude Opus 5.5 in OpenCode, from our server's logs and a VM repro.

Reproduced on Linux at 8acbefefc5 with the real server and an isolated login profile that sleeps eight seconds before adding the provider directory. Startup logged ETIMEDOUT. Removing the delay let a fresh login shell find the executable, but server.refreshProviders still reported it missing. Restarting with the fast profile made the executable discoverable.

Synced from GitHub

Same bug on Arch Linux (Omarchy), bash, triggered by the background service's auto-update restart. Recurring since at least Oct 23.

Environment

  • Version the bug happened on: 0.1.8-nightly.20261107.4301 (service launcher 0.0.46-nightly.20261015.3158). Triage CLI: 0.1.2.
  • Linux x64, kernel 7.2.8 (Omarchy/Arch), Node v24.22.0
  • T3 runs as the systemd user unit t3code.service. The unit has no PATH, so the launcher inherits the user manager's boot-time default /usr/local/bin:/usr/bin.
  • Login shell: bash. Provider CLIs (claude, codex, opencode) and gh are installed via mise under ~/.local/share/mise/installs/..., which only the login shell puts on PATH.
  • Connected from app.t3.codes on another machine via T3 Connect.

What happened The service auto-updated 0.1.8-nightly.20261106.4275 → 20261107.4301 at 06:26 UTC. On the new server's startup, both login-shell candidates timed out:

1[server] Failed to read PATH from login shell /usr/bin/bash. spawnSync /usr/bin/bash ETIMEDOUT
2[server] Failed to read PATH from login shell /bin/bash. spawnSync /bin/bash ETIMEDOUT

The server kept /usr/local/bin:/usr/bin for its whole run. Claude reported "claude was not found on PATH", Codex "Could not start Codex CLI (codex)", OpenCode "not installed or not on PATH", and GitHub PR discovery failed with GitHubCliMissingError because gh is also under mise. Only Antigravity, which doesn't depend on PATH, still worked.

Once the system was idle, the same bash -ilc probe returned the full PATH in about 4ms. systemctl --user restart t3code.service fixed it. That matches the report above: the shell is only slow at the moment of startup, and nothing retries afterward.

Frequency: boot-service.log has this pair of ETIMEDOUT lines after restarts on Oct 23, Oct 26 (×2), Oct 29, Oct 30, Nov 1 (×2), Nov 6 (×2) and Nov 7. So it happens on a large share of service restarts here, and self-updates make it more likely because the new server starts while the machine is busy.

Notes

  • On Arch, /usr/bin/bash and /bin/bash are the same binary, so the fallback candidate just spends another 5s on the same slow shell.
  • Even with PATH restored, the first provider status checks right after the restart timed out (codex --version takes about 4.6s and opencode --version about 3.5s here under mise). Startup load hurts these probes too.
  • Workaround: a systemd drop-in that sets Environment=PATH=... on t3code.service.

Triaged with t3 triage by Claude Code (Claude Opus 5.5, claude-opus-5-5).

Synced from GitHub

Second scenario on Ubuntu Linux, stable v0.1.7: CLI installed after the service started, no login-shell timeout involved

Same one-time PATH read in hydratePosixPath (apps/server/src/os-jank.ts), but here the login-shell read succeeded at startup. The PATH it captured was simply out of date a few minutes later, and nothing re-reads it until a restart.

Environment

  • Version the bug happened on: 0.1.7 stable (server and triage CLI both 0.1.7).
  • Linux x64, kernel 7.0.0-42-generic, Node v26.12.1.
  • T3 runs as the systemd user unit t3code.service (t3 service install). The unit sets no PATH, so the launcher inherits the user manager's default /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin.
  • Login shell: bash. OpenCode 2.2.6 installed by its installer into ~/.opencode/bin, added to PATH in ~/.bashrc.
  • OpenCode instance enabled in settings, managed server mode, no binaryPath or instance env vars.

Timeline (UTC, 2026-11-08)

116:21:53 t3code.service starts; login-shell PATH read succeeds (no warnings in boot-service.log)
216:27:41 OpenCode installer creates ~/.opencode/bin and appends it to ~/.bashrc
316:31:22 Provider re-check: probeOpenCodeBinary -> spawn opencode ENOENT
4 provider status cache: status "error", installed false,
5 "OpenCode CLI (`opencode`) is not installed or not on PATH."
616:37:xx `t3 service restart`; next check: installed true, version 2.2.6, 34 models, ready

bash -lc 'echo $PATH' run at 16:31 already listed ~/.opencode/bin, so a fresh read would have found the binary. The server kept the 16:21 snapshot.

1[cause]: PlatformError: NotFound: ChildProcess.spawn (opencode --version)
2 [cause]: Error: spawn opencode ENOENT

Why it matters for this issue The proposed fix of re-reading the login shell on server.refreshProviders (or when a probe fails with ENOENT while the live PATH still lacks the shell's entries) would cover this case too. Installing a provider CLI after the service is up is a normal first-run flow, and the UI currently gives no hint that a restart is needed.

Workaround t3 service restart after installing or moving a provider CLI.

Written by Claude Fable 5.1 via t3 triage (Claude Code), from this machine's logs and a source read of v0.1.7.