charmbracelet/bubbletea · error

bubbletea: error flushing screen writer: %w

Error message

bubbletea: error flushing screen writer: %w

What it means

Returned by the cursed renderer's periodic flush when s.scr.Flush() fails while publishing a rendered frame (the common per-framerate write path, not just shutdown). It means the differential screen updates for the current frame could not be written to the terminal buffer. This error surfaces through the renderer error channel and typically terminates the event loop wrapped in ErrProgramKilled.

Source

Thrown at cursed_renderer.go:481

	if cur := view.Cursor; cur != nil {
		// MoveTo must come after [uv.TerminalRenderer.Render] because the
		// cursor position might get updated during rendering.
		s.scr.MoveTo(view.Cursor.X, view.Cursor.Y)
	} else if !view.AltScreen {
		// We don't want the cursor to be dangling at the end of the line in
		// inline mode because it can cause unwanted line wraps in some
		// terminals. So we move it to the beginning of the next line if
		// necessary.
		// This is only needed when the cursor is hidden because when it's
		// visible, we already set its position above.
		x, y := s.scr.Position()
		if x >= s.width-1 {
			s.scr.MoveTo(0, y)
		}
	}

	if err := s.scr.Flush(); err != nil {
		return fmt.Errorf("bubbletea: error flushing screen writer: %w", err)
	}

	// Check if we have any render updates to flush.
	hasUpdates := s.buf.Len() > 0

	// Cursor visibility.
	didShowCursor := s.lastView != nil && s.lastView.Cursor != nil
	showCursor := view.Cursor != nil
	hideCursor := !showCursor
	shouldUpdateCursorVis := (s.lastView == nil || didShowCursor != showCursor) || shouldUpdateAltScreen

	// Build final output buffer with synchronized output or hide/show cursor
	// updates. But first, enter/exit alt screen mode if needed.
	//
	// Here, we have two scenarios:
	// 1. Synchronized output updates are supported. In this case, we want to
	//    wrap all updates, unless it's just a cursor visibility change, in
	//    synchronized output mode. This is because synchronized output mode

View on GitHub (pinned to 351d2159f8)

Solutions

  1. Verify the output writer is valid and open for the whole run
  2. Wrap runs that may lose their consumer with context cancellation so the program exits cleanly instead of erroring each frame
  3. For headless/non-TTY usage prefer tea.WithoutRenderer() or run with output to os.Stdout without a TTY
  4. Inspect the wrapped error: EPIPE/ErrClosed usually means the consumer exited — handle as exit condition, not a bug

Example fix

// before
p := tea.NewProgram(m, tea.WithOutput(sock)) // socket may die mid-frame
if _, err := p.Run(); err != nil { log.Fatal(err) }

// after
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGPIPE)
defer cancel()
p := tea.NewProgram(m, tea.WithContext(ctx), tea.WithOutput(sock))
_, err := p.Run()
if err != nil && !isClosedErr(err) { log.Fatal(err) }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the output before starting the loop:
if _, err := w.Write(nil); err != nil {
    return fmt.Errorf("output unusable: %w", err)
}
p := tea.NewProgram(m, tea.WithOutput(w))

Type guard

func isDeadPipe(err error) bool {
    return errors.Is(err, syscall.EPIPE) || errors.Is(err, os.ErrClosed) || errors.Is(err, syscall.EIO)
}

Try / catch

_, err := p.Run()
if err != nil {
    var outerErr error
    if errors.As(err, &outerErr) || true {
        if isDeadPipe(errors.Unwrap(err)) {
            os.Exit(0) // terminal went away; normal end
        }
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Terminal/pty closed or resized away between frames; tea.WithOutput writer erroring during normal rendering; EPIPE from a dead pipe consumer; device IO errors on Windows console handles; disk-full when output is redirected to a file.

Common situations: User closes the terminal window mid-run; program output piped into a tool that exits; remote pty (SSH/Wish) torn down; writing frames to a log file on a full filesystem; flaky serial-console deployments.

Related errors


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