charmbracelet/bubbletea · error
bubbletea: error flushing update to the writer: %w
Error message
bubbletea: error flushing update to the writer: %w
What it means
Returned when the renderer's composed update buffer (cursor moves, erase sequences, synchronized-output wrappers) cannot be copied to the output writer during a flush with updates. This is the write of the actual frame delta after the screen buffer flush succeeded, so it fires specifically when the final io.Copy(s.w, &buf) of a frame fails. It propagates up and commonly ends the program wrapped as ErrProgramKilled.
Source
Thrown at cursed_renderer.go:569
}
if hasUpdates {
// Close synchronized output mode.
buf.WriteString(ansi.ResetModeSynchronizedOutput)
}
} else if (shouldUpdateCursorVis && showCursor) || (hasUpdates && showCursor && didShowCursor) {
_, _ = buf.WriteString(ansi.SetModeTextCursorEnable)
}
// Reset internal screen renderer buffer.
s.buf.Reset()
// If our updates flush buffer has content, write it to the output writer.
if buf.Len() > 0 {
if s.logger != nil {
s.logger.Printf("output: %q", buf.String())
}
if _, err := io.Copy(s.w, &buf); err != nil {
return fmt.Errorf("bubbletea: error flushing update to the writer: %w", err)
}
}
s.lastView = &view
return nil
}
// render implements renderer.
func (s *cursedRenderer) render(v View) {
s.mu.Lock()
defer s.mu.Unlock()
s.view = v
}
// reset implements renderer.
func (s *cursedRenderer) reset() {View on GitHub (pinned to 351d2159f8)
Solutions
- Ensure the consumer of the output stream lives at least as long as Program.Run
- Guard custom writers: return os.ErrClosed instead of panicking, and keep them thread-safe
- Use tea.WithoutRenderer() for programmatic (non-visual) runs
- Treat EPIPE on the output as a termination signal and quit the program gracefully via p.Quit()
Example fix
// before
type sockWriter struct{ c net.Conn }
func (s sockWriter) Write(b []byte) (int, error) {
return s.c.Write(b) // errors kill the frame flush
}
// after
type sockWriter struct{ c net.Conn; dead atomic.Bool }
func (s sockWriter) Write(b []byte) (int, error) {
if s.dead.Load() { return 0, os.ErrClosed }
n, err := s.c.Write(b)
if isDisconnect(err) { s.dead.Store(true) }
return n, err
} Defensive patterns
Strategy: try-catch
Validate before calling
// Same pre-flight as flush: ensure writer accepts writes before Run
if _, err := w.Write([]byte("")); err != nil { return err } Type guard
func isUpdateWriteFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "error flushing update to the writer")
} Try / catch
_, err := p.Run()
if isUpdateWriteFailure(err) {
if inner := errors.Unwrap(errors.Unwrap(err)); isDeadPipe(inner) {
return // output consumer exited
}
log.Printf("render update failed: %v", err)
} Prevention
- Make custom writers thread-safe and non-panicking; return os.ErrClosed when dead
- Pair custom writers with a health check (ping write) on reconnect
- Quit the program when output dies instead of letting every frame error
- Keep pty masters open for the program's lifetime in tests
When it happens
Trigger: Output writer error during an update flush: broken pipe, closed pty, network writer failure, full disk for file output. Distinct from error 6 in that the internal screen flush succeeded but delivering the delta to the writer failed.
Common situations: Terminal emulator quit while the app was rendering; `app | grep -q found` style pipelines where the reader exits; custom WithOutput implementations (tee writers, sockets) erroring under load; slow consumers causing writes after close.
Related errors
- bubbletea: error closing screen writer: %w
- bubbletea: error writing to screen: %w
- bubbletea: error flushing screen writer: %w
- bubbletea: error writing insert above to the writer: %w
- error writing to output: %w
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/2136d2cf6b6caffe.
Report an issue: GitHub.