And of course, the concerns of the serial line driver are thrown into the mix too, for the added entertainment.
- SIGWINCH doesn't always fire on initial spawn — the TUI starts up thinking it has 0 columns and emits garbage until the first real resize. Fix: synthesize an ioctl(TIOCSWINSZ) before the first read, and re-send on focus events. - xterm.js negotiates dimensions with the PTY backend over a non-obvious dance; off-by-one in the cell math wraps long prompts in the wrong place every time. - Tools that detect "am I in a TTY" via isatty() behave differently from tools that stat() stdin; a few agents fall through to non-TUI mode if the PTY's mode bits aren't quite right.
None of that is reflected in the abstract "PTY is a virtual terminal" mental model. The kernel/terminal/application split is a leaky abstraction in practice — you only find out by hosting one inside the other.
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?
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.
It's an interesting question for CIS URLs whether a url-path with an empty final component, that duplicates a url-path with an actual 'index' component (of some kind), is to be taken as the primary or the secondary form. When a site has 'index.php', 'index.html', 'index.gopher', 'index.gemini', and others, the question is whether the container is the same as the index file.