Skip to content

Re-enter raw mode after piped input ends - #431

Open
anandghegde wants to merge 1 commit into
antonmedv:masterfrom
anandghegde:fix-raw-mode-after-piped-input
Open

anandghegde wants to merge 1 commit into
antonmedv:masterfrom
anandghegde:fix-raw-mode-after-piped-input

Conversation

@anandghegde

Copy link
Copy Markdown

Fixes #415

I could reproduce this on macOS with node 26: node -e 'process.stdout.write(JSON.stringify({"a":1}))' | fx renders, but keys get echoed at the bottom and nothing responds. It's a race, which is probably why it works on some machines.

Cause: node saves the terminal settings (termios) when it starts and restores them when it exits (ResetStdio in node). fx starts the TUI before node has exited, so bubbletea puts the terminal in raw mode, and then node's exit puts it back into canonical + echo mode. cat file | fx never hits this because cat doesn't touch the terminal.

The same thing happens with any producer that saves and restores terminal state. For example, this Python script breaks fx every time:

import termios, sys, time
a = termios.tcgetattr(2)          # save at startup
sys.stdout.write('{"a":1}'); sys.stdout.flush()
time.sleep(0.5)
termios.tcsetattr(2, termios.TCSANOW, a)  # restore at exit

Fix: the producer's stdout only reaches EOF after the process has exited, so after the restore. On eofMsg, fx now opens /dev/tty and puts it back into raw mode (term.MakeRaw, the same call bubbletea uses). If the terminal is already raw this does nothing. On exit, bubbletea still restores the original state it saved, so the shell is left normal. If /dev/tty can't be opened (e.g. on Windows), it's a no-op.

Testing: I ran fx inside a pty from a small Python harness. The harness answers fx's OSC 11 and cursor-position queries, checks the tty's ICANON/ECHO flags 2s after start, then sends q:

producer before after
node -e ... 18/20 runs broken (ICANON+ECHO on, q ignored) 20/20 OK, q exits
termios save/restore script above 3/3 broken 3/3 OK
echo '{"a":1}' OK OK

stty -a after fx exits shows icanon echo, so the terminal is restored properly. go test ./... passes, and GOOS=windows / GOOS=linux builds succeed.

This doesn't address the leaked ^[]11;rgb:...^[[48;1R seen on Ubuntu. That's when node's exit lands during the startup background-color query.

Implemented with Claude Code; I reviewed and tested the change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MA8JGs7pCoXFL2W6jEJ8sD

Programs like node save the terminal state at startup and restore it on
exit. When their output is piped into fx, that restore can land after
bubbletea has switched the terminal to raw mode, putting it back into
canonical/echo mode: keys are echoed at the bottom of the screen and
navigation stops working.

The producer's output only reaches EOF once it has exited, so put the
terminal back into raw mode when fx sees EOF.

Fixes antonmedv#415
@anandghegde
anandghegde force-pushed the fix-raw-mode-after-piped-input branch from b589884 to ef62717 Compare September 15, 2026 16:57
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.

Piping any stdout from node.js to fx stdin causes non-functional UI state

1 participant