hashicorp/nomad · error
not a terminal
Error message
not a terminal
What it means
The nomad alloc exec command puts the local terminal in raw mode so that keystrokes like Ctrl+C are forwarded to the remote process. This error is thrown by setRawTerminal when the stream passed to it (os.Stdin) is not attached to a TTY, detected via term.GetFdInfo. Without a real terminal, raw-mode capture and interactive forwarding are impossible.
Source
Thrown at command/alloc_exec.go:316
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
go func() {
for range signalCh {
cancelFn()
}
}()
return client.Allocations().Exec(ctx,
alloc, task, tty, command, stdin, stdout, stderr, sizeCh, nil)
}
// setRawTerminal sets the stream terminal in raw mode, so process captures
// Ctrl+C and other commands to forward to remote process.
// It returns a cleanup function that restores terminal to original mode.
func setRawTerminal(stream any) (cleanup func(), err error) {
fd, isTerminal := term.GetFdInfo(stream)
if !isTerminal {
return nil, errors.New("not a terminal")
}
state, err := term.SetRawTerminal(fd)
if err != nil {
return nil, err
}
return func() { term.RestoreTerminal(fd, state) }, nil
}
// setRawTerminalOutput sets the output stream in Windows to raw mode,
// so it disables LF -> CRLF translation.
// It's basically a no-op on unix.
func setRawTerminalOutput(stream any) (cleanup func(), err error) {
fd, isTerminal := term.GetFdInfo(stream)
if !isTerminal {
return nil, errors.New("not a terminal")
}View on GitHub (pinned to 482b49bf1a)
Solutions
- Allocate a TTY: use `ssh -t` when connecting remotely, or `docker run -it` / `docker exec -it` when inside a container.
- If stdin redirection is intended, add `-i` (no stdin) so the command does not try to attach stdin and enter raw mode.
- Run the command from an interactive terminal session instead of a script.
Example fix
// before $ cat input.txt | nomad alloc exec -job web /bin/sh // after $ nomad alloc exec -i -job web /bin/sh # or run from a real TTY with an interactive shell
Defensive patterns
Strategy: validation
Validate before calling
if term.IsTerminal(int(os.Stdin.Fd())) {
// safe to run interactive exec
runAllocExec()
} else {
runAllocExecWithNoStdin() // add -i
} Type guard
func isTTY(f *os.File) bool { return term.IsTerminal(int(f.Fd())) } Try / catch
if err := runAllocExec(); err != nil && strings.Contains(err.Error(), "not a terminal") {
// fall back to non-interactive mode with -i
} Prevention
- Always add -i when running nomad alloc exec in scripts or CI.
- Use ssh -t when relaying through SSH so a TTY exists.
- Never pipe stdin into interactive exec commands.
When it happens
Trigger: Running `nomad alloc exec` with stdin redirected (e.g. `nomad alloc exec ... < file`), piped from another command, or run from a script/CI runner/cron job where stdin is not a TTY.
Common situations: CI pipelines (Jenkins, GitHub Actions), SSH without -t, piping stdin from a heredoc or echo, running inside a non-interactive shell or docker exec without -t.
Related errors
- need an executable to run
- failed to open a tty: %v
- failed to set command tty: %v
- failed to start command: %v
- command is not present
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/69634e2794058698.
Report an issue: GitHub.