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
- If your shell is left in raw mode, run `stty sane` (or `reset`) to fix it immediately
- Ensure Program.Run is allowed to finish (don't SIGKILL; use ctx cancel or p.Quit) so restoration completes
- Close custom tty files only after Run returns
- 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
- Always let Run reach its shutdown (avoid SIGKILL on TUIs)
- Close tty files only after Run returns
- Keep pty masters alive in tests until shutdown completes
- Tell users about `stty sane`/`reset` in your README troubleshooting section
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
- program was killed
- bubbletea: error closing screen writer: %w
- bubbletea: error writing to screen: %w
- bubbletea: error opening TTY: %w
- bubbletea: error getting terminal size: %w
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/15875c4fa796fcb3.
Report an issue: GitHub.