[Bug]: SSH environment setup evaluates Bash fnm output through POSIX sh#13047

openedSep 22, 2026byRRaghav-56

Area

packages/ssh

Steps to reproduce

  1. Use a Linux SSH host whose sh is dash (for example, Ubuntu/Debian).
  2. Have fnm installed and available to the remote non-interactive shell.
  3. In the desktop client, choose Add Environment → SSH.
  4. Connect to the host.

Expected behavior

The SSH environment setup should work with the remote shell selected by the command, or fail with a clear compatibility diagnostic.

Actual behavior

Environment setup fails with:

text
1Could not prepare the SSH environment: Error invoking remote method 'desktop:ensure-ssh-environment': SshCommandError: sh: 1: eval: --: not found
2sh: 16: eval: Syntax error: "(" unexpected

The remote account may have Bash as its login shell; the failure still occurs because the desktop SSH runner explicitly invokes:

text
1ssh ... sh -l -s -- ...

Root cause

packages/ssh/src/tunnel.ts sends the generated launch and pairing scripts through sh. The generated node-environment script contains Bash-specific fnm activation:

sh
1eval "$(fnm env --shell bash)"

When sh resolves to dash, the Bash-shaped output is evaluated by a POSIX shell. This produces the reported eval: --: not found and Syntax error: "(" unexpected messages.

Relevant code:

Suggested direction

Keep the remote script contract internally consistent:

  • either make the fnm activation path POSIX-shell-safe before evaluating it, or
  • invoke the generated script with Bash when the generated payload requires Bash syntax, with an intentional fallback/diagnostic for hosts without Bash.

Please preserve support for remote hosts that provide only a POSIX shell.

Environment

  • Client: T3 Code desktop
  • Remote: Ubuntu/Linux
  • Remote login shell: Bash
  • Remote sh: dash
  • Failure occurs during desktop:ensure-ssh-environment

Related but distinct: #11001, #4761, and #11733.

Synced from GitHub

I encountered this on a Windows machine while adding an SSH connection to a remote Linux machine through the T3 Code desktop client. The remote account uses Bash as its login shell,

my agent wrote this issue, idk why example of dash was chosen, most prolly the code is only written for bash / linux specific shell commands, needs a diff one for powershell.

Triage

The desktop launch really does force POSIX sh on the remote, but the stderr in this report is not coming from eval "$(fnm env --shell bash)" inside ensure_remote_node_path.

What matches the error

launchOrReuseRemoteServer in packages/ssh/src/tunnel.ts runs:

ts
1remoteCommandArgs: ["sh", "-l", "-s", "--", remoteStateKey(target)]

On Ubuntu/Debian that sh is dash, even when the account’s login shell is Bash. -l sources the login profile before the bootstrap script runs.

These two lines are dash’s eval diagnostic for a script whose first line is -- and whose line 16 uses Bash function name() syntax:

text
1sh: 1: eval: --: not found
2sh: 16: eval: Syntax error: "(" unexpected

A syntax error in eval exits dash (status 2) even with || true. That is enough to fail desktop:ensure-ssh-environment.

What does not match

REMOTE_NODE_ENV_SCRIPT does contain:

sh
1eval "$(fnm env --shell bash)" >/dev/null 2>&1 || true

That came from #2641: fnm has no sh shell, and --use-on-cd emits Bash [[ ]], so the remote script asks for Bash syntax and then tries to ignore failure. Two facts keep it from being this report:

  1. Release desktop passes archiveVersion (apps/desktop/src/main.ts). The archive launch and REMOTE_RUNNER_SCRIPT do not call ensure_remote_node_path. That function runs only for the dev node-script runner.
  2. The fnm eval discards stderr. On dash, the same syntax error under >/dev/null 2>&1 is hidden, and the shell still exits. The messages here were visible, so they came from an eval that is not redirected — login startup, not this line.

fnm 1.39 fnm env --shell bash (with and without --use-on-cd) is POSIX export lines plus functions dash can define. Evaluating it under dash succeeds. The stock installer block (eval "\fnm env`"in~/.bashrc`) also survives dash. So a current fnm env script is not this 16-line body.

Not duplicates

IssueWhy it is different
#11001Same ensure-SSH wrapper and sh -l -s, but the hang is bash -ilc stopped by SIGTTOU
#4761 / #7213#7213 added this login sh so per-user PATH loads. That -l is what now executes the profile under dash
#3709 / #3710Version-manager gates when a non-satisfying node is already on PATH
#11733Worktree setup color queries

Next step

Please run these from the client and paste both stderr outputs:

sh
1ssh user@host 'sh -c true'
2ssh user@host 'sh -l -c true'

If only the login command fails, this is Bash syntax in the login profile (or a file it sources), not the silenced fnm line. Please also paste any eval / fnm / starship block from ~/.profile, ~/.bashrc, and /etc/profile.

The bootstrap script should stay POSIX so a dash-only host still works. The login shell used to load PATH should not be forced to dash when the profile is Bash. Changing only fnm env --shell bash will not fix a release-client failure with this stderr. fnm env --json is still a reasonable follow-up for the dev node-script runner, separate from this report.

Synced from GitHub

Follow-up from the affected Windows client and remote Linux host. I ran the requested checks with the host and username omitted:

text
1ssh <user>@<host> 'sh -c "printf SH_OK"'
2exit 0
3stdout: SH_OK
4
5ssh <user>@<host> 'sh -l -c "printf LOGIN_OK"'
6exit 2
7stderr:
8<starship path omitted> sh: 1: eval: --: not found
9sh: 16: eval: Syntax error: "(" unexpected

This reproduces the failure only when the remote login shell is invoked with -l. The client machine is Windows; the remote machine is Linux.

Synced from GitHub
bash
1ssh <user>@<host> 'sh -c true'
2# No output; succeeded
3
4ssh <user>@<host> 'sh -l -c true'
5/usr/local/bin/starship
6sh: 1: eval: --: not found
7sh: 16: eval: Syntax error: "(" unexpected
8
9Relevant login-profile lines:
10
11.bashrc:34: [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
12.bashrc:80: test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
13.bashrc:123: # Starship
14.bashrc:124: eval "$(starship init bash)"
15.bashrc:126: eval "$(zoxide init bash)"