SIGN IN SIGN UP

ssh/tailssh: fix exit-status frame ordering

Send exit-status before EOF before CHANNEL_CLOSE per RFC 4254 §6.10.
The old code raced CloseWrite against Exit; macOS OpenSSH often saw
EOF first and dropped the exit code.

Replace atomic.Bool/atomic.Int32/channel sync with a sync.WaitGroup
over the output goroutines and emit:

    cmd.Wait -> ss.Exit -> closeAll(childPipes) -> wg.Wait -> ss.Close

The stdin copier stays out of wg: it blocks reading from the SSH
channel until the client half-closes, and many clients (go-scp,
plain `ssh host cmd`, scp, sftp) don't half-close before they
receive CHANNEL_CLOSE from us. Including it deadlocks. It self-
cleans via deferred ss.Close.

Align exit codes with shell convention:
  255 SSH-internal error (OpenSSH ssh.c:1693)
  254 recording-infrastructure failure
  127 command-not-found (POSIX)

Switch session termination from SIGKILL to SIGHUP, matching
OpenSSH PTY-master-close (session.c:2246). Process-group delivery
follows in a later commit.

Relies on the gliderssh wire-order change (tailscale/gliderssh#8),
which go.mod already pins since 3f5eb3199.

Based on tailscale/tailscale#18331.

Updates #18256

Signed-off-by: James Tucker <james@tailscale.com>
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
J
James Tucker committed
92f43003fc6caee00f4cb752b3435904f14ec098
Parent: 12fcb22
Committed by Kristoffer Dalby <kristoffer@dalby.cc> on 7/30/2026, 8:18:00 AM