charmbracelet/bubbletea · warning
error writing to output: %w
Error message
error writing to output: %w
What it means
Returned by Program.flush (tea.go:1234) when writing the accumulated command buffer (p.outputBuf) to p.output fails. This buffer carries terminal control sequences submitted via p.execute / p.batch — bracketed paste setup, mode queries (2026/2027), keyboard enhancements — so failing here means control sequences could not be delivered. It surfaces during shutdown's restoreTerminalState which deliberately ignores it, or during runtime where it can feed the kill path.
Source
Thrown at tea.go:1234
_, _ = p.outputBuf.WriteString(seq)
p.mu.Unlock()
}
// flush flushes the output buffer to the program output.
func (p *Program) flush() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.outputBuf.Len() == 0 {
return nil
}
if p.logger != nil {
p.logger.Printf("output: %q", p.outputBuf.String())
}
_, err := p.output.Write(p.outputBuf.Bytes())
p.outputBuf.Reset()
if err != nil {
return fmt.Errorf("error writing to output: %w", err)
}
return nil
}
// shutdown performs operations to free up resources and restore the terminal
// to its original state.
func (p *Program) shutdown(kill bool) {
p.shutdownOnce.Do(func() {
p.cancel()
// Wait for all handlers to finish.
p.handlers.shutdown()
// Check if the cancel reader has been setup before waiting and closing.
if p.cancelReader != nil {
// Wait for input loop to finish.
if p.cancelReader.Cancel() {
if !kill {View on GitHub (pinned to 351d2159f8)
Solutions
- Keep p.output valid and open through the whole Run lifecycle
- Skip terminal capability queries when output is not a TTY (the library gates most, but custom p.execute calls are yours)
- Handle EPIPE as a shutdown signal, not an error to log loudly
- For scripted/headless runs use WithoutRenderer and don't queue control sequences
Example fix
// before
p.execute(ansi.RequestModeSynchronizedOutput) // queued, flush may fail
// after
if term.IsTerminal(os.Stdout.Fd()) {
p.execute(ansi.RequestModeSynchronizedOutput)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Only queue control sequences on real terminals:
func isTTY(w io.Writer) bool {
if f, ok := w.(*os.File); ok {
return term.IsTerminal(f.Fd())
}
return false
} Type guard
func isOutputFlushFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "error writing to output")
} Try / catch
_, err := p.Run()
if isOutputFlushFailure(err) {
if isDeadPipe(errors.Unwrap(err)) { err = nil }
}
if err != nil { log.Print(err) } Prevention
- Gate p.execute/ANSI queries on term.IsTerminal
- Keep output writer open through shutdown (restoreTerminalState flushes queued commands but ignores errors)
- Install SIGPIPE handling in pipelines
- Avoid sharing output writers across processes
When it happens
Trigger: p.execute(...) queue flush hitting a closed/broken output writer; mode-query writes at startup (RequestModeSynchronizedOutput) when output is dead; custom tea.WithOutput writer erroring; EPIPE because the pipe consumer exited.
Common situations: Non-TTY output combined with mode queries; pipes closing early (`| head`); SSH/pty teardown racing startup; writers shared between processes that get closed by the other side.
Related errors
- bubbletea: error writing to screen: %w
- bubbletea: error flushing screen writer: %w
- bubbletea: error flushing update to the writer: %w
- bubbletea: error writing insert above to the writer: %w
- program experienced a panic
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/c1b595fdddf61fbf.
Report an issue: GitHub.