Summary
When T3 Code starts a remote server over SSH, it runs the command in a non-interactive, non-login shell. On a stock Ubuntu/Debian host, ~/.bashrc returns early for non-interactive shells:
bash
case $- in
i) ;;
*) return;;
esac
Every per-user install location — ~/.local/bin, nvm, an npm prefix, mise/asdf, pnpm — is added to PATH below that guard, so none of it survives. The remote server comes up with only the stock system PATH and reports:
Claude Agent CLI (claude) is not installed or not on PATH.
The binary is installed, on PATH, and works fine in any normal shell on that host. Only T3's launch environment can't see it.
This affects every provider, not just Claude — codex was equally unresolvable on the same host for the same reason.
Impact
The remote connection is unusable for any agent installed the way the vendors' own installers install them. The Claude Code native installer puts claude in ~/.local/bin; nvm-based npm globals land under ~/.nvm/.... None of these are visible to the remote backend on a default Ubuntu host.
The error message actively points away from the cause: it says "not installed", so the natural response is to reinstall the CLI — which changes nothing, because the reinstall lands in the same invisible directory.
Local and WSL backends are unaffected, which makes it look like a remote-host problem rather than a shell-environment one.
Reproduction
Ubuntu host, default ~/.bashrc
Install Claude Code with the official native installer → ~/.local/bin/claude, with export PATH=" 𝐻 𝑂 𝑀 𝐸 / . 𝑙 𝑜 𝑐 𝑎 𝑙 / 𝑏 𝑖 𝑛 : HOME/.local/bin:PATH" appended to ~/.bashrc (i.e. below the non-interactive guard)
Verify it works normally: ssh host then claude --version → prints a version
Add that host as a remote connection in T3 Code
Expected: Claude provider available, same as local/WSL Actual: "Claude Agent CLI (claude) is not installed or not on PATH"
Evidence
The binary is present and runs fine by absolute path
$ ssh seed@10.20.20.10 '/home/seed/.local/bin/claude --version'
2.1.220 (Claude Code)
$ ssh seed@10.20.20.10 'ls -la ~/.local/bin/claude'
/home/seed/.local/bin/claude -> /home/seed/.local/share/claude/versions/2.1.220
But it isn't on PATH in the shell T3 uses
$ ssh seed@10.20.20.10 'echo "PATH=$PATH"; command -v claude; command -v codex'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
Both command -v calls return nothing. That's the stock system PATH with no user-local entries at all.
Why: the export sits below the guard
$ ssh seed@10.20.20.10 'grep -n local/bin ~/.bashrc ~/.profile'
/home/seed/.bashrc:131:export PATH="$HOME/.local/bin:$PATH"
/home/seed/.bashrc:132:export PATH="$HOME/.local/bin:$PATH"
/home/seed/.profile:26: PATH="$HOME/.local/bin:$PATH"
.bashrc line 131 is never reached (guard is at the top). .profile line 26 would work — but .profile is only read by login shells, and T3's invocation isn't one.
Confirmed against the running remote server's actual environment
The remote server process tree is:
npm exec t3@0.0.29 serve --host 127.0.0.1 --port 3773 --base-dir /home/seed/.t3
└─ sh -c t3 serve ...
└─ node /home/seed/.npm/_npx//node_modules/.bin/t3 serve ...
Reading /proc//environ before the fix:
PATH=/home/seed/.npm/_npx//node_modules/.bin:...:/usr/local/sbin:/usr/local/bin:
/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
npm's own injected node_modules/.bin entries, then the stock system PATH. No ~/.local/bin.
After moving the export to line 1 of ~/.bashrc (above the guard) and restarting the remote server:
PATH=/home/seed/.npm/_npx//node_modules/.bin:...:/home/seed/.local/bin:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:...
/home/seed/.local/bin now present, and the Claude Agent CLI health check failed warnings stop appearing in desktop.trace.ndjson entirely.
This also confirms T3's SSH invocation does source ~/.bashrc (bash does this when stdin is a network socket) — it's specifically the non-interactive guard that defeats it.
Suggested fixes
Launch the remote server through a login shell. ssh host 'bash -lc ""' instead of ssh host ''. This is what VS Code Remote-SSH and similar tools do, and it's the highest-value fix: stock Ubuntu's ~/.profile already adds ~/.local/bin, so a login shell would have made this work out of the box with zero user configuration. Worth noting the current process tree already has an sh -c hop, so the shell layer exists — it's just not a login shell.
Allow a per-connection environment override. A PATH or env-vars field in the remote connection settings would cover nvm/asdf/mise setups that need more than a login shell.
Surface binaryPath per connection in the UI. Today the workaround is hand-editing providerInstances..config.binaryPath in the remote's /.t3/userdata/settings.json over SSH. That's discoverable only if you already know the remote keeps its own settings file.
Make the error message diagnostic instead of misleading. "not installed or not on PATH" sends users to reinstall. Including the PATH that was actually searched, and the shell used to search it, would make the cause self-evident:
Claude Agent CLI (claude) not found.
Searched PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:...
Shell: non-login, non-interactive (ssh seed@host )
Note: per-user install dirs (/.local/bin, nvm, npm prefix) are not on this PATH.
(1) alone would resolve the common case. (4) is cheap and would save a lot of misdirected debugging regardless.
Workaround
On the remote host, move the PATH export above the non-interactive guard:
bash
cp ~/.bashrc ~/.bashrc.bak
sed -i '|export PATH="$HOME/.local/bin:$PATH"|d' ~/.bashrc
sed -i '1i export PATH="$HOME/.local/bin:$PATH"' ~/.bashrc
Verify from the client, using the same non-interactive path T3 uses:
bash
ssh user@host 'command -v claude'
Then restart the remote server so the health check re-runs — it only runs at server startup.
Note: kill on the npm exec parent does not cascade to the node server; it must be killed directly, or you end up with two servers on the same port and base-dir.
Shell-independent alternative: sudo ln -s ~/.local/bin/claude /usr/local/bin/claude.
Environment
Client: Windows 11, T3 Code Alpha
Remote: Ubuntu, remote server t3@0.0.29 launched via npm exec over SSH
Provider: Claude Code 2.1.220, native installer (~/.local/bin/claude → ~/.local/share/claude/versions/2.1.220)
Local and WSL backends on the same client work correctly
Minor, related: secrets in argv on the remote
The spawned agent process carries its MCP bearer token in its command line:
claude --output-format stream-json ... --mcp-config {"mcpServers":{"t3-code":{...,
"headers":{"Authorization":"Bearer "}}}} ...
/proc//cmdline is world-readable on Linux by default, so any local user can read that token with ps — no root required. It grants access to the T3 MCP endpoint on 127.0.0.1:3773. Passing it via an environment variable or a file descriptor would avoid the exposure. Happy to file separately if preferred.
Summary
When T3 Code starts a remote server over SSH, it runs the command in a non-interactive, non-login shell. On a stock Ubuntu/Debian host, ~/.bashrc returns early for non-interactive shells:
bash
case $- in
i) ;;
*) return;;
esac
Every per-user install location — ~/.local/bin, nvm, an npm prefix, mise/asdf, pnpm — is added to PATH below that guard, so none of it survives. The remote server comes up with only the stock system PATH and reports:
Claude Agent CLI (claude) is not installed or not on PATH.
The binary is installed, on PATH, and works fine in any normal shell on that host. Only T3's launch environment can't see it.
This affects every provider, not just Claude — codex was equally unresolvable on the same host for the same reason.
Impact
The remote connection is unusable for any agent installed the way the vendors' own installers install them. The Claude Code native installer puts claude in ~/.local/bin; nvm-based npm globals land under ~/.nvm/.... None of these are visible to the remote backend on a default Ubuntu host.
The error message actively points away from the cause: it says "not installed", so the natural response is to reinstall the CLI — which changes nothing, because the reinstall lands in the same invisible directory.
Local and WSL backends are unaffected, which makes it look like a remote-host problem rather than a shell-environment one.
Reproduction
Ubuntu host, default ~/.bashrc
Install Claude Code with the official native installer → ~/.local/bin/claude, with
export PATH=" 𝐻 𝑂 𝑀 𝐸 / . 𝑙 𝑜 𝑐 𝑎 𝑙 / 𝑏 𝑖 𝑛 : HOME/.local/bin:PATH"appended to ~/.bashrc (i.e. below the non-interactive guard)Verify it works normally: ssh host then claude --version → prints a version
Add that host as a remote connection in T3 Code
Expected: Claude provider available, same as local/WSL Actual: "Claude Agent CLI (claude) is not installed or not on PATH"
Evidence
The binary is present and runs fine by absolute path
$ ssh seed@10.20.20.10 '/home/seed/.local/bin/claude --version'
2.1.220 (Claude Code)
$ ssh seed@10.20.20.10 'ls -la ~/.local/bin/claude'
/home/seed/.local/bin/claude -> /home/seed/.local/share/claude/versions/2.1.220
But it isn't on PATH in the shell T3 uses
$ ssh seed@10.20.20.10 'echo "PATH=$PATH"; command -v claude; command -v codex'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
Both command -v calls return nothing. That's the stock system PATH with no user-local entries at all.
Why: the export sits below the guard
$ ssh seed@10.20.20.10 'grep -n local/bin ~/.bashrc ~/.profile'
/home/seed/.bashrc:131:export PATH="$HOME/.local/bin:$PATH"
/home/seed/.bashrc:132:export PATH="$HOME/.local/bin:$PATH"
/home/seed/.profile:26: PATH="$HOME/.local/bin:$PATH"
.bashrc line 131 is never reached (guard is at the top). .profile line 26 would work — but .profile is only read by login shells, and T3's invocation isn't one.
Confirmed against the running remote server's actual environment
The remote server process tree is:
npm exec t3@0.0.29 serve --host 127.0.0.1 --port 3773 --base-dir /home/seed/.t3
└─ sh -c t3 serve ...
└─ node /home/seed/.npm/_npx//node_modules/.bin/t3 serve ...
Reading /proc//environ before the fix:
PATH=/home/seed/.npm/_npx//node_modules/.bin:...:/usr/local/sbin:/usr/local/bin:
/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
npm's own injected node_modules/.bin entries, then the stock system PATH. No ~/.local/bin.
After moving the export to line 1 of ~/.bashrc (above the guard) and restarting the remote server:
PATH=/home/seed/.npm/_npx//node_modules/.bin:...:/home/seed/.local/bin:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:...
/home/seed/.local/bin now present, and the Claude Agent CLI health check failed warnings stop appearing in desktop.trace.ndjson entirely.
This also confirms T3's SSH invocation does source ~/.bashrc (bash does this when stdin is a network socket) — it's specifically the non-interactive guard that defeats it.
Suggested fixes
Launch the remote server through a login shell. ssh host 'bash -lc ""' instead of ssh host ''. This is what VS Code Remote-SSH and similar tools do, and it's the highest-value fix: stock Ubuntu's ~/.profile already adds ~/.local/bin, so a login shell would have made this work out of the box with zero user configuration. Worth noting the current process tree already has an sh -c hop, so the shell layer exists — it's just not a login shell.
Allow a per-connection environment override. A PATH or env-vars field in the remote connection settings would cover nvm/asdf/mise setups that need more than a login shell.
Surface binaryPath per connection in the UI. Today the workaround is hand-editing providerInstances..config.binaryPath in the remote's
/.t3/userdata/settings.json over SSH. That's discoverable only if you already know the remote keeps its own settings file./.local/bin, nvm, npm prefix) are not on this PATH.Make the error message diagnostic instead of misleading. "not installed or not on PATH" sends users to reinstall. Including the PATH that was actually searched, and the shell used to search it, would make the cause self-evident:
Claude Agent CLI (
claude) not found.Searched PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:...
Shell: non-login, non-interactive (ssh seed@host )
Note: per-user install dirs (
(1) alone would resolve the common case. (4) is cheap and would save a lot of misdirected debugging regardless.
Workaround
On the remote host, move the PATH export above the non-interactive guard:
bash
cp ~/.bashrc ~/.bashrc.bak
sed -i '|export PATH="$HOME/.local/bin:$PATH"|d' ~/.bashrc
sed -i '1i export PATH="$HOME/.local/bin:$PATH"' ~/.bashrc
Verify from the client, using the same non-interactive path T3 uses:
bash
ssh user@host 'command -v claude'
Then restart the remote server so the health check re-runs — it only runs at server startup.
Note: kill on the npm exec parent does not cascade to the node server; it must be killed directly, or you end up with two servers on the same port and base-dir.
Shell-independent alternative: sudo ln -s ~/.local/bin/claude /usr/local/bin/claude.
Environment
Client: Windows 11, T3 Code Alpha
Remote: Ubuntu, remote server t3@0.0.29 launched via npm exec over SSH
Provider: Claude Code 2.1.220, native installer (~/.local/bin/claude → ~/.local/share/claude/versions/2.1.220)
Local and WSL backends on the same client work correctly
Minor, related: secrets in argv on the remote
The spawned agent process carries its MCP bearer token in its command line:
claude --output-format stream-json ... --mcp-config {"mcpServers":{"t3-code":{...,
"headers":{"Authorization":"Bearer "}}}} ...
/proc//cmdline is world-readable on Linux by default, so any local user can read that token with ps — no root required. It grants access to the T3 MCP endpoint on 127.0.0.1:3773. Passing it via an environment variable or a file descriptor would avoid the exposure. Happy to file separately if preferred.