Why terminal animations can eat actual output and how to stop them

Wait 5 sec.

Terminal animation is usually done as a "clear-then-draw" dance. The most common "clear" sequence is "\r\033[K", which returns the cursor to the start of the line and erases to end of line. Then you write the new frame. This is fine in isolation. The problem is that the clear sequence has no idea what's on that line. If your program wrote something there, or wrote a newline the animator didn't account for, the clear either erases your data or misses the frame entirely and leaves it stranded on screen. Most CLI programs hit this the moment they log anything while a spinner is running. stdout and stderr are usually separate file descriptors - sure, but more often than not they display in the same physical terminal, so spinning on one and printing to the other still breaks the animation. As far as I'm aware, there are two usual answers: Manage only the stream you animate on, and accept that writes to the other one corrupt the display. This is usually the default approach most projects start with. Go to raw mode and own the whole terminal - which works, and costs you a TUI framework. Arguably, the majority of CLI tools do not need a full TUI. I know mine did not. This is why I decided to jump into this rabbit hole and build my own library to handle both streams gracefully while staying out of the application's way as much as possible. The library coordinates any two streams behind a single mutex-protected write path: nothing reaches the terminal without clearing the frame first, and it tracks whether the last write ended in a newline so the animation always ends up on the last line instead of appended to application output. Both streams stay independently addressable, pipeable and redirectable. However, while writing this library, I met a more interesting problem. One that is genuinely unsolvable under the chosen constraints. Resizing. Try resizing your terminal while anything is animating on it - and you either lose lines or get a wall of junk rows piling up. The problem is tri-fold. SIGWINCH that UNIX-like systems provide is delivered asynchronously, with no guarantee of it being visible before the next write into the resized terminal. Moreover, TIOCGWINSZ - the syscall to get current terminal width, is a point-in-time snapshot, and no terminal offers a "here's the width, reject my write if it changed" handshake or a way to prevent the user from resizing the terminal. And finally, there's reflowing. It's when the terminal, upon resizing, can insert line breaks into already written output to make what was previously just one line take up more vertical space under the new width: old line at one width can become old line at one width which for our use case means we can have something like this (imagine we're drawing a [=======>] looking progress bar): [=========== //   submitted by