charmbracelet/bubbletea · error

bubbletea: error flushing update to the writer

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.

Solutions

  1. Ensure the consumer of the output stream lives at least as long as Program.Run
  2. Guard custom writers: return os.ErrClosed instead of panicking, and keep them thread-safe
  3. Use tea.WithoutRenderer() for programmatic (non-visual) runs
  4. 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

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


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

Appendix: 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)