{"record":{"id":"bf1617fde272c37f","repo":"charmbracelet/bubbletea","slug":"bubbletea-error-flushing-screen-writer-w","errorCode":null,"errorMessage":"bubbletea: error flushing screen writer: %w","messagePattern":"bubbletea: error flushing screen writer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cursed_renderer.go","lineNumber":481,"sourceCode":"\tif cur := view.Cursor; cur != nil {\n\t\t// MoveTo must come after [uv.TerminalRenderer.Render] because the\n\t\t// cursor position might get updated during rendering.\n\t\ts.scr.MoveTo(view.Cursor.X, view.Cursor.Y)\n\t} else if !view.AltScreen {\n\t\t// We don't want the cursor to be dangling at the end of the line in\n\t\t// inline mode because it can cause unwanted line wraps in some\n\t\t// terminals. So we move it to the beginning of the next line if\n\t\t// necessary.\n\t\t// This is only needed when the cursor is hidden because when it's\n\t\t// visible, we already set its position above.\n\t\tx, y := s.scr.Position()\n\t\tif x >= s.width-1 {\n\t\t\ts.scr.MoveTo(0, y)\n\t\t}\n\t}\n\n\tif err := s.scr.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"bubbletea: error flushing screen writer: %w\", err)\n\t}\n\n\t// Check if we have any render updates to flush.\n\thasUpdates := s.buf.Len() > 0\n\n\t// Cursor visibility.\n\tdidShowCursor := s.lastView != nil && s.lastView.Cursor != nil\n\tshowCursor := view.Cursor != nil\n\thideCursor := !showCursor\n\tshouldUpdateCursorVis := (s.lastView == nil || didShowCursor != showCursor) || shouldUpdateAltScreen\n\n\t// Build final output buffer with synchronized output or hide/show cursor\n\t// updates. But first, enter/exit alt screen mode if needed.\n\t//\n\t// Here, we have two scenarios:\n\t// 1. Synchronized output updates are supported. In this case, we want to\n\t//    wrap all updates, unless it's just a cursor visibility change, in\n\t//    synchronized output mode. This is because synchronized output mode","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/charmbracelet/bubbletea/blob/351d2159f8d8a85613aa2a6e98c8c63df3c98623/cursed_renderer.go#L463-L499","documentation":"Returned by the cursed renderer's periodic flush when s.scr.Flush() fails while publishing a rendered frame (the common per-framerate write path, not just shutdown). It means the differential screen updates for the current frame could not be written to the terminal buffer. This error surfaces through the renderer error channel and typically terminates the event loop wrapped in ErrProgramKilled.","triggerScenarios":"Terminal/pty closed or resized away between frames; tea.WithOutput writer erroring during normal rendering; EPIPE from a dead pipe consumer; device IO errors on Windows console handles; disk-full when output is redirected to a file.","commonSituations":"User closes the terminal window mid-run; program output piped into a tool that exits; remote pty (SSH/Wish) torn down; writing frames to a log file on a full filesystem; flaky serial-console deployments.","solutions":["Verify the output writer is valid and open for the whole run","Wrap runs that may lose their consumer with context cancellation so the program exits cleanly instead of erroring each frame","For headless/non-TTY usage prefer tea.WithoutRenderer() or run with output to os.Stdout without a TTY","Inspect the wrapped error: EPIPE/ErrClosed usually means the consumer exited — handle as exit condition, not a bug"],"exampleFix":"// before\np := tea.NewProgram(m, tea.WithOutput(sock)) // socket may die mid-frame\nif _, err := p.Run(); err != nil { log.Fatal(err) }\n\n// after\nctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGPIPE)\ndefer cancel()\np := tea.NewProgram(m, tea.WithContext(ctx), tea.WithOutput(sock))\n_, err := p.Run()\nif err != nil && !isClosedErr(err) { log.Fatal(err) }","handlingStrategy":"try-catch","validationCode":"// Pre-flight the output before starting the loop:\nif _, err := w.Write(nil); err != nil {\n    return fmt.Errorf(\"output unusable: %w\", err)\n}\np := tea.NewProgram(m, tea.WithOutput(w))","typeGuard":"func isDeadPipe(err error) bool {\n    return errors.Is(err, syscall.EPIPE) || errors.Is(err, os.ErrClosed) || errors.Is(err, syscall.EIO)\n}","tryCatchPattern":"_, err := p.Run()\nif err != nil {\n    var outerErr error\n    if errors.As(err, &outerErr) || true {\n        if isDeadPipe(errors.Unwrap(err)) {\n            os.Exit(0) // terminal went away; normal end\n        }\n    }\n    log.Fatal(err)\n}","preventionTips":["Detect consumer death (SIGPIPE handling) and call p.Quit promptly","Avoid tea.WithOutput to streams with independent lifetimes","For file output, monitor ENOSPC and alert the user","Test render paths with a bytes.Buffer writer"],"tags":["bubbletea","renderer","flush","frame","io","go"],"backgroundTag":null,"analyzedSha":"351d2159f8d8a85613aa2a6e98c8c63df3c98623","analyzedAt":"2026-08-15T10:48:22.366Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}