{"record":{"id":"c1b595fdddf61fbf","repo":"charmbracelet/bubbletea","slug":"error-writing-to-output-w","errorCode":null,"errorMessage":"error writing to output: %w","messagePattern":"error writing to output: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"tea.go","lineNumber":1234,"sourceCode":"\t_, _ = p.outputBuf.WriteString(seq)\n\tp.mu.Unlock()\n}\n\n// flush flushes the output buffer to the program output.\nfunc (p *Program) flush() error {\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\tif p.outputBuf.Len() == 0 {\n\t\treturn nil\n\t}\n\tif p.logger != nil {\n\t\tp.logger.Printf(\"output: %q\", p.outputBuf.String())\n\t}\n\t_, err := p.output.Write(p.outputBuf.Bytes())\n\tp.outputBuf.Reset()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing to output: %w\", err)\n\t}\n\treturn nil\n}\n\n// shutdown performs operations to free up resources and restore the terminal\n// to its original state.\nfunc (p *Program) shutdown(kill bool) {\n\tp.shutdownOnce.Do(func() {\n\t\tp.cancel()\n\n\t\t// Wait for all handlers to finish.\n\t\tp.handlers.shutdown()\n\n\t\t// Check if the cancel reader has been setup before waiting and closing.\n\t\tif p.cancelReader != nil {\n\t\t\t// Wait for input loop to finish.\n\t\t\tif p.cancelReader.Cancel() {\n\t\t\t\tif !kill {","sourceCodeStart":1216,"sourceCodeEnd":1252,"githubUrl":"https://github.com/charmbracelet/bubbletea/blob/351d2159f8d8a85613aa2a6e98c8c63df3c98623/tea.go#L1216-L1252","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\np.execute(ansi.RequestModeSynchronizedOutput) // queued, flush may fail\n\n// after\nif term.IsTerminal(os.Stdout.Fd()) {\n    p.execute(ansi.RequestModeSynchronizedOutput)\n}","handlingStrategy":"try-catch","validationCode":"// Only queue control sequences on real terminals:\nfunc isTTY(w io.Writer) bool {\n    if f, ok := w.(*os.File); ok {\n        return term.IsTerminal(f.Fd())\n    }\n    return false\n}","typeGuard":"func isOutputFlushFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"error writing to output\")\n}","tryCatchPattern":"_, err := p.Run()\nif isOutputFlushFailure(err) {\n    if isDeadPipe(errors.Unwrap(err)) { err = nil }\n}\nif err != nil { log.Print(err) }","preventionTips":["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"],"tags":["bubbletea","output","control-sequences","io","go"],"backgroundTag":null,"analyzedSha":"351d2159f8d8a85613aa2a6e98c8c63df3c98623","analyzedAt":"2026-08-15T10:48:22.366Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}