{"record":{"id":"707e9d38cf65e400","repo":"netbirdio/netbird","slug":"open-terminal-w","errorCode":null,"errorMessage":"open terminal: %w","messagePattern":"open terminal: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/ssh.go","lineNumber":608,"sourceCode":"\t\treturn fmt.Errorf(\"execute command: %w\", err)\n\t}\n\treturn nil\n}\n\n// openSSHTerminal opens an interactive SSH terminal.\nfunc openSSHTerminal(ctx context.Context, c *sshclient.Client) error {\n\tif err := c.OpenTerminal(ctx); err != nil {\n\t\tif errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n\t\t\treturn nil\n\t\t}\n\n\t\tvar exitMissingErr *ssh.ExitMissingError\n\t\tif errors.As(err, &exitMissingErr) {\n\t\t\tlog.Debugf(\"Remote terminal exited without exit status: %v\", err)\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"open terminal: %w\", err)\n\t}\n\treturn nil\n}\n\n// startPortForwarding starts local and remote port forwarding based on command line flags\nfunc startPortForwarding(ctx context.Context, c *sshclient.Client, cmd *cobra.Command) error {\n\tfor _, forward := range localForwards {\n\t\tif err := parseAndStartLocalForward(ctx, c, forward, cmd); err != nil {\n\t\t\treturn fmt.Errorf(\"local port forward %s: %w\", forward, err)\n\t\t}\n\t}\n\n\tfor _, forward := range remoteForwards {\n\t\tif err := parseAndStartRemoteForward(ctx, c, forward, cmd); err != nil {\n\t\t\treturn fmt.Errorf(\"remote port forward %s: %w\", forward, err)\n\t\t}\n\t}\n","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/ssh.go#L590-L626","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","solutions":["Confirm the peer is reachable (`netbird status -d`) and retry — dropped overlay connections are the most common cause.","If you do not need interactivity, run a one-shot command instead: `netbird ssh <host> <command>` (executeSSHCommand path) or avoid --tty flags.","Check that your own terminal is a real TTY (`[ -t 0 ]`) when launching; CI/non-interactive shells should use command mode.","For peers that cannot allocate PTYs, disable PTY requests per the CLI flags for that invocation.","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."],"exampleFix":"# before\nnetbird ssh peer1        # inside a CI step with no TTY\n# -> open terminal: open /dev/tty: no such device or address\n\n# after\nnetbird ssh peer1 -- uname -a   # non-interactive command mode, no TTY needed","handlingStrategy":"try-catch","validationCode":"// only attempt interactive mode from a real TTY\nif !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) {\n\treturn errors.New(\"no local TTY: use `netbird ssh <host> -- <command>` instead\")\n}\nif !peerConnected(host) {\n\treturn fmt.Errorf(\"peer %s not connected\", host)\n}","typeGuard":"func hasInteractiveTTY() bool {\n\treturn term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))\n}","tryCatchPattern":"if err := openSSHTerminal(sshCtx, c); err != nil {\n\tvar exitMissing *ssh.ExitMissingError\n\tswitch {\n\tcase errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):\n\t\treturn nil // user hit Ctrl-C / timeout: normal end of session\n\tcase errors.As(err, &exitMissing):\n\t\treturn nil // remote closed without status\n\tdefault:\n\t\treturn fmt.Errorf(\"open terminal: %w\", err)\n\t}\n}","preventionTips":["Detect TTY availability at script start and fall back to command mode instead of failing terminal setup.","Keep interactive sessions alive with input or enable keepalives so NAT idle timeouts do not kill the channel mid-shell.","Retry once on a clean reconnect if the overlay dropped; persistent failure for one peer means remote-side PTY/shell policy — check the peer.","Do not run `netbird ssh <host>` (interactive form) from cron/CI; use the command form."],"tags":["go","ssh","cli","terminal","pty"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}