charmbracelet/bubbletea · error
bubbletea: error closing screen writer: %w
Error message
bubbletea: error closing screen writer: %w
What it means
Returned by the cursed renderer's close path when the underlying screen writer's Flush fails while tearing down the renderer (Program shutting down or being closed). The close sequence writes terminal reset sequences (mouse modes, title, cursor style, colors, unicode mode 2027) and this error means those bytes could not be flushed to the output writer. It indicates the output channel (terminal, pty, or redirected writer) rejected writes at shutdown.
Source
Thrown at cursed_renderer.go:223
if lv.BackgroundColor != nil {
_, _ = s.scr.WriteString(ansi.ResetBackgroundColor)
}
if lv.ForegroundColor != nil {
_, _ = s.scr.WriteString(ansi.ResetForegroundColor)
}
if lv.ProgressBar != nil && lv.ProgressBar.State != ProgressBarNone {
_, _ = s.scr.WriteString(ansi.ResetProgressBar)
}
}
if s.cellbuf.Method == ansi.GraphemeWidth {
// Make sure to turn off Unicode mode (2027)
_, _ = s.scr.WriteString(ansi.ResetModeUnicodeCore)
}
if err := s.scr.Flush(); err != nil {
return fmt.Errorf("bubbletea: error closing screen writer: %w", err)
}
if s.buf.Len() > 0 {
if s.logger != nil {
s.logger.Printf("output: %q", s.buf.String())
}
if _, err := io.Copy(s.w, &s.buf); err != nil {
return fmt.Errorf("bubbletea: error writing to screen: %w", err)
}
s.buf.Reset()
}
x, y := s.scr.Position()
// We want to clear the renderer state but not the cursor position. This is
// because we might be putting the tea process in the background, run some
// other process, and then return to the tea process. We want to keep the
// cursor position so that we can continue where we left off.View on GitHub (pinned to 351d2159f8)
Solutions
- Ensure the output writer stays open until Program.Run returns
- If piping/redirecting intentionally, use tea.WithoutRenderer() or tea.WithInput(nil) for non-TTY runs instead of a full TUI
- Check for EPIPE and treat it as a benign shutdown error if the consumer is allowed to exit early
- If over SSH, handle disconnects by cancelling the program context so shutdown happens while the pty is still alive
Example fix
// before
p := tea.NewProgram(m, tea.WithOutput(w))
// w gets closed elsewhere while p.Run() is still shutting down
// after
done := make(chan struct{})
go func() { p.Run(); close(done) }()
// close w only after Run finished
closeWriterAfter(done) Defensive patterns
Strategy: fallback
Validate before calling
// Validate the output channel before shutdown can hit it:
func isWritable(w io.Writer) bool {
if f, ok := w.(*os.File); ok {
return f.Fd() >= 0 // still has an fd
}
return true
} Type guard
func isClosedWrite(err error) bool {
return errors.Is(err, os.ErrClosed) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET)
} Try / catch
if err := runErr; err != nil {
if strings.Contains(err.Error(), "error closing screen writer") && isClosedWrite(errors.Unwrap(err)) {
return // consumer exited: benign
}
log.Print(err)
} Prevention
- Close custom output writers only after Program.Run returns
- For piped runs use tea.WithoutRenderer()
- Add write-timeouts on custom writers so shutdown can't hang
- Treat EPIPE on close-flush as expected in pipelines
When it happens
Trigger: Program shutdown when stdout is a closed pipe or pty; running with tea.WithOutput to a network connection that dropped; a terminal that was closed before the program exited; EPIPE/ErrClosed from the output file during the final Flush in renderer close.
Common situations: Piping output to `head` or another short-lived process that exits first; SSH session disconnected before graceful shutdown; test harness closing the fake output writer early; running a Bubble Tea program with output redirected to a file on a full disk.
Related errors
- bubbletea: error writing to screen: %w
- bubbletea: error flushing screen writer: %w
- bubbletea: error flushing update to the writer: %w
- program was killed
- bubbletea: error restoring console: %w
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/8663c2f9e5bca71a.
Report an issue: GitHub.