[Bug]: SSH environment setup hangs 90s when the remote session inherits a controlling TTY — bash -ilc PATH probe is stopped by SIGTTOU
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Area
apps/desktop
Steps to reproduce
The trigger is a remote host where the SSH session inherits a controlling TTY that the session's process group does not own. This happens whenever the remote SSH daemon (in my case tailscaled's built-in SSH server) was itself started from an interactive terminal rather than by an init/entrypoint, so it holds e.g. pts/2 as its controlling terminal and passes it to every session it spawns.
To reproduce on such a host:
- Add an SSH environment pointing at it (Add Environment → SSH).
- Click Add environment.
- Wait 90 s.
Minimal repro of the underlying condition, independent of T3 Code — on the affected host:
$ ssh -n user@host 'bash -ilc "printenv PATH"' # hangs forever
$ ssh -n user@host 'bash -lc "printenv PATH"' # returns instantly
$ ssh -n user@host 'setsid bash -ilc "printenv PATH"' # returns instantly
$ ssh -tt user@host 'bash -ilc "printenv PATH"' # returns instantly
Only the interactive (-i) variant without a TTY hangs. It is not shell-specific — /bin/sh (dash) hangs identically, and it reproduces with --noprofile --norc, so no startup file is involved.
Expected behavior
ensure-ssh-environment completes, or fails fast with a diagnostic naming the probe that hung.
Actual behavior
The dialog spins for 90 s and reports:
Could not prepare the SSH environment: Error invoking remote method
'desktop:ensure-ssh-environment': SshCommandError: SSH command timed out after 90000ms.
Root cause. The login-shell probe in the environment-capture path runs:
{ probe: "login-shell", command: shell, args: ["-ilc", capturePosixEnvironmentCommand(names)], timeout: LOGIN_SHELL_TIMEOUT }
-i makes the shell initialize job control, which calls tcsetpgrp() on the inherited controlling TTY from a background process group. The kernel responds with SIGTTOU and the shell is stopped before it runs anything.
Observed on the remote while the dialog was spinning:
PID STAT WCHAN COMMAND
2121 T do_signal_stop sh -l -s -- <token>
2170 Tl do_signal_stop node .../t3 serve --host 127.0.0.1 --port <port> --base-dir ~/.t3
2171 Tl do_signal_stop node - <port> 60000 1000
2313 T do_signal_stop /bin/bash -ilc printf '%s\n' '__T3CODE_ENV_PATH_START__'; printenv PATH || true; ...
STAT=T / WCHAN=do_signal_stop — stopped on a job-control signal, not slow. Session context confirming the inherited TTY:
$ ssh -n user@host 'ps -o tty=,pgid=,tpgid= -p $$'
pts/2 221 219 # controlling tty pts/2, foreground pgrp is 219 (not ours)
On a host where the same daemon was started by the container entrypoint, the identical probe succeeds, because there is no controlling terminal:
? 93 -1
Both hosts run the same tailscaled 1.102.3 with identical --has-tty=false --force-v1-behavior session flags, so this is not a Tailscale version difference.
Note this is invisible to normal use: plain ssh host <command> is non-interactive and never touches job control, so users see a host they can SSH into by hand that T3 Code cannot attach to.
Suggested fix
Drop -i from the probe. A PATH/env capture does not need an interactive shell — bash -lc already sources the login files, and it returns instantly on the affected host. If interactive-only rc files must be picked up, setsid before the shell also avoids the stop.
Impact
Blocks adding the environment entirely; the host is unusable from T3 Code. Two secondary problems make it worse:
- The stopped remote process group is leaked on timeout. Each attempt leaves a wedged
sh -l -sgroup plus itsnode t3 serveandbash -ilcchildren in stateTon the remote, forever. I accumulated ~15 stopped processes across 5 attempts. Killing the localssh.exeafter the timeout does not reap them. - A permanently unreachable saved environment is retried without backoff. A separate saved environment whose host had been offline for 6 h respawned
ssh.execontinuously (observed at18:21:00and again at18:21:26, each burningConnectTimeout=10). Possibly related to #4144.
Version or commit
0.0.40 (Windows desktop, T3 Code (Alpha).exe ProductVersion 0.0.40.0)
Environment
- Client: Windows 11 Pro 26200, OpenSSH_for_Windows_9.5p2
- Remote: Ubuntu 26.04.1 LTS devcontainer, bash 5.3.9, node v22.22.1, reached over Tailscale SSH (
tailscaled1.102.3, userspace networking) - Working comparison host: Ubuntu 22.04.5 LTS devcontainer, bash 5.1.16, same
tailscaled1.102.3
Logs or stack traces
Local process captured while the dialog was hanging:
ssh.exe -o BatchMode=yes -o ConnectTimeout=10 -p 22 user@host sh -l -s -- <token>
The process spawns immediately on click and stays alive until the 90 s timeout, so connection setup and auth are fine; the stall is entirely in the remote setup script.
Workaround
Ensure the remote SSH daemon has no controlling terminal — start it from the container entrypoint, or restart it under setsid. Once the session no longer inherits a foreign controlling TTY, bash -i degrades gracefully to "no job control in this shell" and the probe completes.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/desktop by locating the ensure-ssh-environment environment-capture path and its login-shell probe. Reproduce with the provided SSH commands on a host inheriting a controlling TTY, then inspect how the probe invokes the shell and handles timeout. Done means the affected probe completes without the 90-second hang and does not leave the remote process group stopped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, typescript
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100