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

  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.