wavetermdev/waveterm · error

failed to start command: %w

Error message

failed to start command: %w

What it means

After building the full command string, Start() calls session.Start(fullCmd) on the golang.org/x/crypto/ssh session. If the remote side refuses to begin execution of the command (channel/request error at the SSH protocol level), the error is wrapped as 'failed to start command'. The session channel exists but the exec request failed.

Source

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

func (s *SSHProcessController) Start() error {
	s.lock.Lock()
	defer s.lock.Unlock()

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

	fullCmd, err := BuildShellCommand(s.cmdSpec)
	if err != nil {
		return fmt.Errorf("failed to build shell command: %w", err)
	}
	// if stdout/stderr weren't piped, then session.stdout/stderr will be nil
	// and the library guarantees that the outputs will be attached to io.Discard
	// if stdin hasn't been piped, then session.stdin will be nil
	// and the libary guarantees that it will be attached to an empty bytes.Buffer, which will produce an immediate EOF
	// tl;dr we don't need to worry about hanging beause of long input or explicitly closing stdin
	if err := s.session.Start(fullCmd); err != nil {
		return fmt.Errorf("failed to start command: %w", err)
	}
	s.started = true
	return nil
}

// Wait waits for the command to complete
func (s *SSHProcessController) Wait() error {
	s.once.Do(func() {
		s.waitErr = s.session.Wait()
	})
	return s.waitErr
}

// Kill terminates the command
func (s *SSHProcessController) Kill() {
	s.lock.Lock()
	defer s.lock.Unlock()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check the wrapped error: 'connection lost' style errors mean reconnect and retry; request-failed means inspect remote sshd restrictions.
  2. Verify the shell/executable named in the CommandSpec exists on the remote host (which bash).
  3. Test the same command manually over plain ssh to rule out forced-command/Match restrictions.
  4. Add keepalives to detect dead connections before Start.

Example fix

// before
err := ctrl.Start() // opaque ssh error, controller unusable
// after
if err := ctrl.Start(); err != nil {
    if strings.Contains(err.Error(), "connection lost") || strings.Contains(err.Error(), "EOF") {
        client = reconnectSSH(cfg)
        ctrl, err = genconn.MakeSSHCmdClient(client, spec)
        if err == nil { err = ctrl.Start() }
    }
    if err != nil { return err }
}
Defensive patterns

Strategy: retry

Validate before calling

func remoteHasShell(client *ssh.Client, shell string) bool {
    out, err := client.NewSession(); if err != nil { return false }
    defer out.Close()
    err = out.Run("command -v " + shell)
    return err == nil
}

Try / catch

if err := ctrl.Start(); err != nil {
    if isConnError(err) { // EOF, "connection lost", "broken pipe"
        client = reconnectSSH(cfg)
        ctrl, _ = genconn.MakeSSHCmdClient(client, spec)
        return ctrl.Start()
    }
    return fmt.Errorf("remote refused exec (check sshd restrictions / shell path): %w", err)
}

Prevention

When it happens

Trigger: Calling Start() when the underlying SSH connection broke between session creation and Start, the remote sshd rejects the exec request, or the requested executable/shell cannot be invoked on the remote side.

Common situations: Network drop right after session creation; remote shell path in CommandSpec does not exist on the host (e.g. zsh not installed); sshd forced-command restrictions rejecting arbitrary exec; server overloaded/out of PTYs.

Related errors


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