hashicorp/nomad · error

failed to open a tty: %v

Error message

failed to open a tty: %v

What it means

The shared executor's execHelper.runTTY allocates a pseudo-terminal (pty) for an interactive exec session via newTerminal(). If opening the tty fails, the error is wrapped as 'failed to open a tty' and the exec session aborts.

Source

Thrown at drivers/shared/executor/exec_utils.go:51

	// processStart starts the process, like `exec.Cmd.Start()`
	processStart func() error

	// processWait blocks until command terminates and returns its final state
	processWait func() (*os.ProcessState, error)
}

func (e *execHelper) run(ctx context.Context, tty bool, stream drivers.ExecTaskStream) error {
	if tty {
		return e.runTTY(ctx, stream)
	}
	return e.runNoTTY(ctx, stream)
}

func (e *execHelper) runTTY(ctx context.Context, stream drivers.ExecTaskStream) error {
	ptyF, tty, err := e.newTerminal()
	if err != nil {
		return fmt.Errorf("failed to open a tty: %v", err)
	}
	defer tty.Close()

	if err := e.setTTY(tty); err != nil {
		return fmt.Errorf("failed to set command tty: %v", err)
	}
	if err := e.processStart(); err != nil {
		return fmt.Errorf("failed to start command: %v", err)
	}

	var wg sync.WaitGroup
	errCh := make(chan error, 3)

	pty, err := ptyF()
	if err != nil {
		return fmt.Errorf("failed to get pty: %v", err)
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Retry without a TTY (non-interactive exec) if interactivity is not required
  2. Check /dev/pts is mounted and ptys available inside the container (mount devpts, raise max)
  3. Verify container/devices config allows /dev/ptmx; relax seccomp if it blocks openpty
  4. Check kernel limits (file descriptors, kernel.pty.max) on the host

Example fix

// before
exec, err := client.AllocExec(ctx, opts) // opts.Tty = true
// after
if ttyUnavailable { opts.Tty = false } // fall back to non-tty exec
exec, err := client.AllocExec(ctx, opts)
Defensive patterns

Strategy: fallback

Validate before calling

// check container has pty support before requesting tty exec
if _, err := os.Stat("/dev/ptmx"); err != nil {
    opts.Tty = false // fall back to non-tty
}

Try / catch

if err := execSession(); err != nil {
    if strings.Contains(err.Error(), "failed to open a tty") {
        opts.Tty = false
        err = execSession() // retry without tty
    }
}

Prevention

When it happens

Trigger: run() selects runTTY when a TTY is requested and newTerminal() (pty open, typically /dev/ptmx) fails — e.g. no free ptys, /dev/ptmx missing, or permission denied inside the container/namespace.

Common situations: Alloc exec with -t / interactive terminal into a container lacking /dev/pts; exhausted pty limits (devpts max); containers started without a device tree; restricted seccomp blocking pty allocation.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/e52c086a27c8388a. Report an issue: GitHub.