slackhq/nebula · error

ErrNoTerminal

ErrNoTerminal

Error message

cannot read password from nonexistent terminal

What it means

ErrNoTerminal is defined in cmd/nebula-cert/passwords.go and returned by StdinPasswordReader.ReadPassword when stdin is not a terminal (term.IsTerminal is false), so an interactive passphrase cannot be prompted. Functions that require interactive encryption (ca, signCert) propagate it to distinguish 'no TTY available' from other password errors.

Source

Thrown at cmd/nebula-cert/passwords.go:11

package main

import (
	"errors"
	"fmt"
	"os"

	"golang.org/x/term"
)

var ErrNoTerminal = errors.New("cannot read password from nonexistent terminal")

type PasswordReader interface {
	ReadPassword() ([]byte, error)
}

type StdinPasswordReader struct{}

func (pr StdinPasswordReader) ReadPassword() ([]byte, error) {
	if !term.IsTerminal(int(os.Stdin.Fd())) {
		return nil, ErrNoTerminal
	}

	password, err := term.ReadPassword(int(os.Stdin.Fd()))
	// Terminal echo is off while reading, so the user's Enter key does not
	// produce a visible newline. Emit one on stderr to match the prompt.
	fmt.Fprintln(os.Stderr)

	return password, err

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Provide the passphrase non-interactively via the tool's passphrase flag/file (e.g. -passphrase or -passphrase-file) so no terminal is needed.
  2. Run inside a real TTY (docker run -it, ssh -t, `script -qc`).
  3. Inject a custom PasswordReader implementation in tests/automation instead of StdinPasswordReader.
  4. If encryption of the output key is not required, disable key encryption so no prompt is attempted.

Example fix

// before: prompts, fails in CI
$ nebula-cert ca -name bastion

// after: supply passphrase non-interactively
$ nebula-cert ca -name bastion -passphrase "$(cat /run/secrets/ca_pass)"
Defensive patterns

Strategy: type-guard

Validate before calling

if !term.IsTerminal(int(os.Stdin.Fd())) {
    // must supply passphrase via flag/file; interactive prompt impossible
    return errors.New("stdin is not a TTY; provide -passphrase or -passphrase-file")
}

Type guard

func canPromptInteractively(f *os.File) bool {
    return term.IsTerminal(int(f.Fd()))
}

Try / catch

passphrase, err := pr.ReadPassword()
if errors.Is(err, ErrNoTerminal) {
    return fmt.Errorf("cannot prompt for passphrase without a TTY; use -passphrase-file")
} else if err != nil {
    return fmt.Errorf("failed reading password: %w", err)
}

Prevention

When it happens

Trigger: ReadPassword called on a non-TTY stdin, e.g. running 'nebula-cert ca ...' without a passphrase flag under cron/systemd/CI, in Docker without -t, or with stdin redirected from a file/pipe (passwords.go:21). ca.go:228 converts it to 'out-key must be encrypted interactively'.

Common situations: Automated jobs invoking nebula-cert expecting to prompt for a passphrase; containerized runs lacking a pseudo-TTY; piping echo 'secret' | into the tool.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/779e6f91de354a8a. Report an issue: GitHub.