wavetermdev/waveterm · error

failed to create SSH session: %w

Error message

failed to create SSH session: %w

What it means

MakeSSHCmdClient calls client.NewSession() on an established golang.org/x/crypto/ssh connection to open a channel for running a command. If the underlying SSH connection cannot open a new session channel (connection dropped, closed, or refused by the server), the error is wrapped as 'failed to create SSH session'. This happens before any command is built or started.

Source

Thrown at pkg/genconn/ssh-impl.go:48

type SSHProcessController struct {
	client      *ssh.Client
	session     *ssh.Session
	lock        *sync.Mutex
	once        *sync.Once
	stdinPiped  bool
	stdoutPiped bool
	stderrPiped bool
	waitErr     error
	started     bool
	cmdSpec     CommandSpec
}

// MakeSSHCmdClient creates a new instance of SSHCmdClient
func MakeSSHCmdClient(client *ssh.Client, cmdSpec CommandSpec) (*SSHProcessController, error) {
	log.Printf("SSH-NEWSESSION (cmdclient)\n")
	session, err := client.NewSession()
	if err != nil {
		return nil, fmt.Errorf("failed to create SSH session: %w", err)
	}
	return &SSHProcessController{
		client:  client,
		lock:    &sync.Mutex{},
		once:    &sync.Once{},
		cmdSpec: cmdSpec,
		session: session,
	}, nil
}

// Start begins execution of the command
func (s *SSHProcessController) Start() error {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.started {
		return fmt.Errorf("command already started")
	}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check/verify the SSH connection is still alive (e.g. client.SendRequest keepalive) before creating the session; if dead, reconnect and rebuild the *ssh.Client.
  2. Retry the operation with a fresh connection — transient network drops are the most common cause.
  3. Raise MaxSessions on the remote sshd_config if many concurrent sessions are needed.
  4. Check server logs (auth.log/sshd -d) to confirm whether the server is rejecting channels.

Example fix

// before
ctrl, err := genconn.MakeSSHCmdClient(client, cmdSpec)
if err != nil { return err }
// after
if _, err := client.SendRequest("keepalive@openssh.com", true, nil); err != nil {
    client, err = reconnectSSH(cfg) // rebuild dead connection
    if err != nil { return err }
}
ctrl, err := genconn.MakeSSHCmdClient(client, cmdSpec)
if err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

func sshAlive(client *ssh.Client) bool {
    _, _, err := client.SendRequest("keepalive@openssh.com", true, nil)
    return err == nil
}
if !sshAlive(client) { client = mustReconnect() }

Try / catch

ctrl, err := genconn.MakeSSHCmdClient(client, cmdSpec)
if err != nil {
    log.Printf("ssh session open failed: %v; reconnecting", err)
    client = reconnectSSH(cfg)
    ctrl, err = genconn.MakeSSHCmdClient(client, cmdSpec)
    if err != nil { return fmt.Errorf("ssh session unavailable: %w", err) }
}

Prevention

When it happens

Trigger: Calling MakeSSHCmdClient (directly or via SSHShellClient.MakeProcessController, used by MakeProcessController and CpWshToRemote) when the *ssh.Client's underlying connection has been closed/reset, the server rejects the session channel (MaxSessions reached), or the connection was never fully established.

Common situations: Remote host restarted or network dropped mid-connection; SSH server's MaxSessions limit hit (default 10); connection idle-timeouted by firewall/server (ClientAliveInterval); trying to reuse a client after a previous command's connection was torn down; server's sshd configured to disallow exec sessions (allowtcpforwarding/no-exec chroot).

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/b7774c3b9069575b. Report an issue: GitHub.