Skip to content

Inherit stdout when it is a TTY so interactive apps keep terminal support - #235

Open
ryanwinchester wants to merge 1 commit into
burrito-elixir:mainfrom
ryanwinchester-forks:fix/234-inherit-stdout-when-tty
Open

Inherit stdout when it is a TTY so interactive apps keep terminal support#235
ryanwinchester wants to merge 1 commit into
burrito-elixir:mainfrom
ryanwinchester-forks:fix/234-inherit-stdout-when-tty

Conversation

@ryanwinchester

Copy link
Copy Markdown

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_tty requires both stdin and stdout to be TTYs, so it disables its tty backend entirely:

probe (run in a real terminal) 1.6.0 (stdout piped) this PR (stdout inherited)
:io_ansi.enabled() (OTP 29) false true
:shell.start_interactive({:noshell, :raw}) {:error, :enotsup} :ok
:io.columns() (after raw) {:error, :enotsup} {:ok, 80}
:io.get_chars(:standard_io, "", 1) never returns {: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 check wrapper.zig already uses for _IS_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)

  1. TTY path: a Tuix TUI app packaged with this branch (plus the custom_erts workaround for Raw mode support? #215) works end to end in a terminal under an automated expect harness: alternate screen, raw keyboard input (arrow keys), diffed rendering, clean exit.
  2. Redirected: ./app </dev/null >out.log 2>&1 takes the pipe path, captures output, exits promptly.
  3. EPIPE regression: an app that floods stdout, piped into head -1, prints one line and exits promptly instead of hanging — the exact feat: Zig 0.16.0 compatibility #225 scenario.

…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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1.6.0: wrapper pipes the BEAM's stdout, breaking TTY detection (raw mode, ANSI, stdin reads) for interactive apps

1 participant