> Tools that detect "am I in a TTY" via isatty() behave differently from tools that stat() stdin
Wait, how do you even use fstat's output to find out if the file is a tty?
Although in my experience the "funniest" part is deciding whether to use isatty() on stdin or on stdout. I mean, there is no much point enabling line editing/tab completion if stdin is a pipe/regular file, right?
Fair pushback — I was being sloppy. The "stat vs isatty" divergence I meant is the older pattern of checking S_ISCHR(st_mode) plus the major number, which some legacy tools still do instead of calling isatty(). Functionally equivalent in most cases, but it can produce slightly different answers on weirder systems (containers, weird /dev/pts mounts).
The stdin-vs-stdout split is where I see the most actual "is this a TTY" mistakes though. Tools that emit JSON-on-stdout-when-piped and TUI-when-not work fine until something stuffs them into a PTY with piped stdin — then they're in TUI mode but can't actually read the user input format they expect.
> The stdin-vs-stdout split is where I see the most actual "is this a TTY" mistakes though. Tools that emit JSON-on-stdout-when-piped and TUI-when-not work fine until something stuffs them into a PTY with piped stdin — then they're in TUI mode but can't actually read the user input format they expect.
Stuff like this is why a build script I used to maintain would redirect stdin from /dev/null when running commands that were intended to be non-interactive. You only need one script to hang forever waiting for a user to type in a password to decide that you'll force the issue going forward.
Same problem flipped: I once watched a CI step hang for 47 minutes because some sub-command popped a `read -p "Continue?"` and there was no controlling TTY to type into and no /dev/null redirect to give it a fast EOF. The fix was the same as yours — `< /dev/null` everywhere, treat any stdin attach as an error.
The really fun version is when a command writes the prompt to stderr (so it shows up in the build log!) and then reads from a stdin you didn't realize was still open. Took embarrassingly long to track down.