charmbracelet/bubbletea · error

bubbletea: error restoring console: %w

Error message

bubbletea: error restoring console: %w

What it means

Returned by restoreInput (tty.go:44) when term.Restore fails to restore the tty INPUT file descriptor to its previously saved state during shutdown. Bubble Tea saved the termios/Windows-console state when entering raw mode; this error means the terminal was left in raw mode because the ioctl to restore it failed. The wrapped error is usually an ENOTTY/EBADF-class failure indicating the fd or kernel state changed.

Source

Thrown at tty.go:44

		return nil
	}
	return p.initInput()
}

// restoreTerminalState restores the terminal to the state prior to running the
// Bubble Tea program.
func (p *Program) restoreTerminalState() error {
	// Flush queued commands.
	_ = p.flush()

	return p.restoreInput()
}

// restoreInput restores the tty input to its original state.
func (p *Program) restoreInput() error {
	if p.ttyInput != nil && p.previousTtyInputState != nil {
		if err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil {
			return fmt.Errorf("bubbletea: error restoring console: %w", err)
		}
	}
	if p.ttyOutput != nil && p.previousOutputState != nil {
		if err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil {
			return fmt.Errorf("bubbletea: error restoring console: %w", err)
		}
	}
	return nil
}

// initInputReader (re)commences reading inputs.
func (p *Program) initInputReader(cancel bool) error {
	if cancel && p.cancelReader != nil {
		p.cancelReader.Cancel()
		p.waitForReadLoop()
	}

	term := p.environ.Getenv("TERM")

View on GitHub (pinned to 351d2159f8)

Solutions

  1. If your shell is left in raw mode, run `stty sane` (or `reset`) to fix it immediately
  2. Ensure Program.Run is allowed to finish (don't SIGKILL; use ctx cancel or p.Quit) so restoration completes
  3. Close custom tty files only after Run returns
  4. For ptys in tests, keep the master open until shutdown completes

Example fix

# after the error, terminal left in raw mode:
# fix (shell):
stty sane && reset

// go: ensure orderly shutdown
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
p := tea.NewProgram(m, tea.WithContext(ctx))
_, _ = p.Run() // restoration happens inside Run's shutdown
Defensive patterns

Strategy: try-catch

Validate before calling

// Before shutdown-heavy runs, confirm the input tty is still restorable:
// (best effort)
func ttyHealthy(f *os.File) bool {
    if f == nil { return true }
    _, _, err := term.GetSize(f.Fd())
    return err == nil
}

Type guard

func isRestoreFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "bubbletea: error restoring console")
}

Try / catch

_, err := p.Run()
if isRestoreFailure(err) {
    fmt.Fprintln(os.Stderr, "terminal may need `stty sane`; run it if input looks off")
    err = nil // non-fatal: program state is still valid
}

Prevention

When it happens

Trigger: The input tty fd was closed before shutdown finished; /dev/tty revoked (vhangup) after SSH detach; program killed with SIGKILL semantics racing restore; a pty whose master side already exited, making tcsetattr return EIO.

Common situations: Terminal window closed mid-run; SSH connection dropped before Run's shutdown completed; test ptys torn down while the program restores state; subsequent shell showing raw-mode symptoms (no echo, garbled input) after a crash.

Related errors


AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15). Data as JSON: /api/errors/15875c4fa796fcb3. Report an issue: GitHub.