netbirdio/netbird · error

open terminal: %w

Error message

open terminal: %w

What it means

Returned by openSSHTerminal when c.OpenTerminal fails to start the interactive shell. Context cancellation/deadline and a remote exit without exit status (ExitMissingError) are already treated as success; this error covers everything else — failure to request a session channel, the shell/PTY request being refused, local TTY setup failing, or the channel breaking during the session.

Source

Thrown at client/cmd/ssh.go:608

		return fmt.Errorf("execute command: %w", err)
	}
	return nil
}

// openSSHTerminal opens an interactive SSH terminal.
func openSSHTerminal(ctx context.Context, c *sshclient.Client) error {
	if err := c.OpenTerminal(ctx); err != nil {
		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
			return nil
		}

		var exitMissingErr *ssh.ExitMissingError
		if errors.As(err, &exitMissingErr) {
			log.Debugf("Remote terminal exited without exit status: %v", err)
			return nil
		}

		return fmt.Errorf("open terminal: %w", err)
	}
	return nil
}

// startPortForwarding starts local and remote port forwarding based on command line flags
func startPortForwarding(ctx context.Context, c *sshclient.Client, cmd *cobra.Command) error {
	for _, forward := range localForwards {
		if err := parseAndStartLocalForward(ctx, c, forward, cmd); err != nil {
			return fmt.Errorf("local port forward %s: %w", forward, err)
		}
	}

	for _, forward := range remoteForwards {
		if err := parseAndStartRemoteForward(ctx, c, forward, cmd); err != nil {
			return fmt.Errorf("remote port forward %s: %w", forward, err)
		}
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Confirm the peer is reachable (`netbird status -d`) and retry — dropped overlay connections are the most common cause.
  2. If you do not need interactivity, run a one-shot command instead: `netbird ssh <host> <command>` (executeSSHCommand path) or avoid --tty flags.
  3. Check that your own terminal is a real TTY (`[ -t 0 ]`) when launching; CI/non-interactive shells should use command mode.
  4. For peers that cannot allocate PTYs, disable PTY requests per the CLI flags for that invocation.
  5. If it fails consistently for one peer only, inspect that peer's sshd/agent logs — a refused shell request is remote-side policy, not a client bug.

Example fix

# before
netbird ssh peer1        # inside a CI step with no TTY
# -> open terminal: open /dev/tty: no such device or address

# after
netbird ssh peer1 -- uname -a   # non-interactive command mode, no TTY needed
Defensive patterns

Strategy: try-catch

Validate before calling

// only attempt interactive mode from a real TTY
if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) {
	return errors.New("no local TTY: use `netbird ssh <host> -- <command>` instead")
}
if !peerConnected(host) {
	return fmt.Errorf("peer %s not connected", host)
}

Type guard

func hasInteractiveTTY() bool {
	return term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))
}

Try / catch

if err := openSSHTerminal(sshCtx, c); err != nil {
	var exitMissing *ssh.ExitMissingError
	switch {
	case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
		return nil // user hit Ctrl-C / timeout: normal end of session
	case errors.As(err, &exitMissing):
		return nil // remote closed without status
	default:
		return fmt.Errorf("open terminal: %w", err)
	}
}

Prevention

When it happens

Trigger: `netbird ssh <host>` (no remote command) where the peer refuses the shell or pty-req channel request, the local terminal cannot be put into raw mode, or the overlay connection drops mid-session. Also when the peer's embedded SSH server cannot allocate a PTY (restricted environment, netstack-mode peers without terminal support).

Common situations: First interactive logins to headless peers that reject PTY allocation; running `netbird ssh` under a non-TTY context (some CI wrappers) where local terminal setup fails; NAT/idle timeouts killing the overlay session mid-shell.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/707e9d38cf65e400. Report an issue: GitHub.