Inherit stdout when it is a TTY so interactive apps keep terminal support - #235
Open
ryanwinchester wants to merge 1 commit into
Open
Conversation
…port The EPIPE fix (burrito-elixir#225) pipes the BEAM's stdout through the wrapper unconditionally on Unix. That means the VM never sees a TTY on stdout, even when the binary is run in a terminal: prim_tty disables its tty backend (it requires both stdin and stdout to be TTYs), so ANSI detection fails, raw mode returns {error, enotsup}, and keyboard input is never delivered. Interactive and TUI applications cannot work. Only interpose the pipe when stdout is not a TTY: - stdout is a tty (interactive session): inherit it, matching the existing Windows behavior. The EPIPE hang cannot occur on a tty. - stdout is not a tty (piped/redirected): keep the burrito-elixir#225 copy-thread behavior. Also guard the copy_thread join in the wait error path, which is now null on the Unix inherit path. Verified on macOS 26 arm64 (zig 0.16.0): - TUI app in a terminal: raw mode ok, keyboard input delivered, alternate screen works end to end - stdout redirected to a file: pipe path taken, output captured, prompt exit - app | head -1: prints one line and exits promptly (EPIPE behavior from burrito-elixir#225 preserved) Fixes burrito-elixir#234
This was referenced Aug 19, 2026
bougyman
added a commit
to rubyists/linear-cli
that referenced
this pull request
Aug 20, 2026
The bug is Burrito 1.6’s new stdout relay from PR #225 (burrito-elixir/burrito#225). It made the wrapper a second owner of stdout. Issue #234 (burrito-elixir/burrito#234) and PR #235 (burrito-elixir/burrito#235) use the practical fix: when stdout is a TTY, let BEAM inherit it directly; retain the relay only for pipes. I ported that fix: - app/mix.exs:1 installs the release hook. - app/release/burrito_patches.exs:1 applies the upstream patch and fails closed if Burrito changes. - app/test/linear_cli/release/burrito_patches_test.exs:1 verifies it. - Linux Burrito binary compiled successfully. - A throttled pseudo-terminal emitted all 5,000 rows, including the final row, then exited 0. - Full suite: 312 tests passed. - Formatter check passed. Need a follow-up on this when burrito gets the patch into mainstream
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #234.
Problem
The EPIPE fix from #225 pipes the BEAM's stdout through the wrapper unconditionally on Unix. The VM therefore never sees a TTY on stdout, even when the binary is run in a terminal. OTP's
prim_ttyrequires both stdin and stdout to be TTYs, so it disables its tty backend entirely::io_ansi.enabled()(OTP 29)falsetrue:shell.start_interactive({:noshell, :raw}){:error, :enotsup}:ok:io.columns()(after raw){:error, :enotsup}{:ok, 80}:io.get_chars(:standard_io, "", 1){:ok, "x"}Interactive and TUI applications cannot work: they can draw (writes pass through the copy thread) but cannot detect the terminal, enter raw mode, or read a single keystroke.
Fix
Only interpose the pipe when stdout is not a TTY (
Io.File.stdout().isTty/1, the same checkwrapper.zigalready uses for_IS_TTY):app | head -5) cannot occur on a tty.Also guards the
copy_thread.?.join()in the wait error path, which is now null on the Unix inherit path.Verification (macOS 26 arm64, Zig 0.16.0)
custom_ertsworkaround for Raw mode support? #215) works end to end in a terminal under an automatedexpectharness: alternate screen, raw keyboard input (arrow keys), diffed rendering, clean exit../app </dev/null >out.log 2>&1takes the pipe path, captures output, exits promptly.head -1, prints one line and exits promptly instead of hanging — the exact feat: Zig 0.16.0 compatibility #225 scenario.