charmbracelet/bubbletea · error

bubbletea: error getting terminal size: %w

Error message

bubbletea: error getting terminal size: %w

What it means

Returned by Program.Run right after terminal init when term.GetSize on the output TTY file descriptor fails, so Bubble Tea cannot learn the initial terminal dimensions to size the first frame. The wrapped error is typically an ioctl (TIOCGWINSZ) failure such as EINVAL or ENOTTY. Program startup aborts and the terminal state is restored.

Source

Thrown at tea.go:1047

				returnErr = fmt.Errorf("%w: %w", ErrProgramKilled, ErrProgramPanic)
				p.recoverFromPanic(r)
			}
		}()
	}

	// Check if output is a TTY before entering raw mode, hiding the cursor and
	// so on.
	if err := p.initTerminal(); err != nil {
		return p.initialModel, err
	}

	// Get the initial window size.
	width, height := p.width, p.height
	if p.ttyOutput != nil {
		// Set the initial size of the terminal.
		w, h, err := term.GetSize(p.ttyOutput.Fd())
		if err != nil {
			return p.initialModel, fmt.Errorf("bubbletea: error getting terminal size: %w", err)
		}

		width, height = w, h
	}

	p.width, p.height = width, height
	resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height}

	if p.renderer == nil {
		if p.disableRenderer {
			p.renderer = &nilRenderer{}
		} else {
			// If no renderer is set use the cursed one.
			r := newCursedRenderer(
				p.output,
				p.environ,
				p.width,
				p.height,

View on GitHub (pinned to 351d2159f8)

Solutions

  1. If output is not a real terminal, pass tea.WithOutput(io.Discard) or os.Stdout with tea.WithoutRenderer()
  2. Set an explicit initial size with tea.WithWindowSize? no — instead pre-set via Program options like tea.WithAltScreen is unrelated: use p's width/height by constructing with tea.WithInput... ultimately: avoid fake TTYs; run inside a real terminal or real pty (e.g. creack/pty in tests)
  3. Retry starting the program after a short delay if the terminal was mid-initialization
  4. Check the wrapped errno: ENOTTY means non-terminal output — switch to non-renderer mode

Example fix

// before
p := tea.NewProgram(m, tea.WithOutput(f)) // f: not a real tty, ioctl fails
_, err := p.Run()

// after
if term.IsTerminal(f.Fd()) {
    p := tea.NewProgram(m, tea.WithOutput(f))
    _, err = p.Run()
} else {
    p := tea.NewProgram(m, tea.WithoutRenderer(), tea.WithOutput(f))
    _, err = p.Run()
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the output fd supports window-size queries before Run:
func supportsWINSZ(f *os.File) bool {
    _, _, err := term.GetSize(f.Fd())
    return err == nil
}
if !supportsWINSZ(out) {
    p := tea.NewProgram(m, tea.WithoutRenderer(), tea.WithOutput(out))
    // ... run in plain mode
}

Type guard

func isGetSizeFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "error getting terminal size")
}

Try / catch

_, err := p.Run()
if isGetSizeFailure(err) {
    if errors.Is(errors.Unwrap(err), syscall.ENOTTY) {
        // fall back to renderer-less mode
        _, err = tea.NewProgram(m, tea.WithoutRenderer()).Run()
    }
}
if err != nil { log.Fatal(err) }

Prevention

When it happens

Trigger: Output is a TTY-like handle that doesn't support the window-size ioctl (some ptys mid-teardown, closed fds, serial consoles); output set via tea.WithOutput to a file/socket that passed TTY checks but fails TIOCGWINSZ; terminal emulator in a transient state during startup; fd limit issues.

Common situations: Running under a half-configured pty in tests; output redirected to /dev/null on some platforms; running inside certain CI pseudo-terminals or `script` wrappers; race between terminal creation and program start; exotic environments (jails, containers) with limited ioctls.

Related errors


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