charmbracelet/bubbletea · error
error entering raw mode: %w
Error message
error entering raw mode: %w
What it means
Bubble Tea failed to put the terminal into raw mode on a Unix-like system while initializing input. initInput() calls term.MakeRaw() on the stdin file descriptor (an ioctl TCGETS/TCSETS on the fd), and the kernel rejected it — typically ENOTTY, EBADF, or EACCES. The wrapped error is returned from Program.Run() via initTerminal(), so the program never starts.
Source
Thrown at tty_unix.go:21
package tea
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/charmbracelet/x/term"
)
func (p *Program) initInput() (err error) {
// Check if input is a terminal
if f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) {
p.ttyInput = f
p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())
if err != nil {
return fmt.Errorf("error entering raw mode: %w", err)
}
// OPTIM: We can use hard tabs and backspaces to optimize cursor
// movements. This is based on termios settings support and whether
// they exist and enabled.
p.checkOptimizedMovements(p.previousTtyInputState)
}
if f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) {
p.ttyOutput = f
}
return nil
}
const suspendSupported = true
// Send SIGTSTP to the entire process group.View on GitHub (pinned to 351d2159f8)
Solutions
- Check the wrapped error's underlying errno: run with the full chain printed (`%+v` or errors.Unwrap) — ENOTTY means the fd is not a real terminal, EBADF means it is closed.
- Ensure stdin is a real tty before starting: if term.IsTerminal(os.Stdin.Fd()) is false, run with tea.WithInput(nil) or a non-tty input so Bubble Tea skips raw mode.
- If you need a tty in a non-interactive context (tests, CI), allocate a pty (e.g. creack/pty) and pass it via tea.WithInput(ptmx) / tea.WithOutput(ptmx).
- For headless rendering, start the program with tea.WithoutRenderer() — initTerminal() returns nil early and raw mode is never attempted.
- Reopen /dev/tty explicitly with tea.OpenTTY() and feed the returned files to WithInput/WithOutput when the original stdin fd is stale or closed.
Example fix
// before
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
log.Fatalf("run: %v", err) // error entering raw mode: inappropriate ioctl for device
}
// after
in, out, err := tea.OpenTTY() // fresh, real terminal handles
if err != nil {
log.Fatalf("open tty: %v", err)
}
defer in.Close()
defer out.Close()
p := tea.NewProgram(model{}, tea.WithInput(in), tea.WithOutput(out)) Defensive patterns
Strategy: try-catch
Validate before calling
if f, ok := stdin.(term.File); ok && !term.IsTerminal(f.Fd()) {
// not a real terminal: raw mode will fail
stdin = nil // or a pipe
}
p := tea.NewProgram(model, tea.WithInput(stdin)) Type guard
func isRealTTY(f term.File) bool {
return term.IsTerminal(f.Fd())
} Try / catch
if _, err := p.Run(); err != nil {
var rawErr error
if strings.Contains(err.Error(), "error entering raw mode") {
rawErr = errors.Unwrap(err)
}
switch {
case rawErr == nil:
log.Fatalf("program error: %v", err)
case errors.Is(rawErr, unix.ENOTTY):
log.Fatal("stdin is not a terminal; run interactively or use tea.WithInput(nil)")
case errors.Is(rawErr, unix.EBADF):
log.Fatal("stdin fd is closed; reopen with tea.OpenTTY()")
default:
log.Fatalf("terminal init failed: %v", err)
}
} Prevention
- Never pipe stdin into a full Bubble Tea program; branch on term.IsTerminal(os.Stdin.Fd()) and use tea.WithInput(nil) for non-interactive runs.
- In tests and CI, allocate a real pty (creack/pty) and pass it via tea.WithInput/WithOutput instead of relying on the runner's stdin.
- Use tea.OpenTTY() when the process may have been started with a stale or redirected stdin.
- For programs that must run headless, start with tea.WithoutRenderer() — it skips raw-mode setup entirely.
When it happens
Trigger: Running a Program whose p.input implements term.File and passes term.IsTerminal(), but whose fd cannot actually be switched to raw mode: a closed stdin fd, a pty that lost its controlling side, a /dev/tty that is not accessible, or a pseudo-terminal granted by a sandbox/container without full termios permissions.
Common situations: Piping or redirecting stdin in shells or CI (input is a pipe but a wrapping pty still reports a terminal), running under `go test` with no real tty, detached processes or daemons that kept a stale terminal fd, containers/seccomp profiles blocking TCSETS ioctls, and SSH sessions that dropped while the program was starting.
Related errors
- error making terminal raw: %w
- error getting terminal state: %w
- bubbletea: error opening TTY: %w
- bubbletea: error getting terminal size: %w
- bubbletea: error restoring console: %w
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/6340e0b5aed71a4a.
Report an issue: GitHub.