charmbracelet/bubbletea · warning
error writing to output
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.
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 flushing screen writer
- bubbletea: error flushing update to the writer
- bubbletea: error writing insert above to the writer
- bubbletea: error writing to screen
- bubbletea: could not create cancelable reader
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/c1b595fdddf61fbf.
Report an issue: GitHub.
Appendix: 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)