Skip to content

fix(cmd): respect SetStdin/SetStdout/SetStderr for tea.Exec compatibility - #522

Merged
taciturnaxolotl merged 6 commits into
charmbracelet:mainfrom
puneet-chandna:main
Jul 30, 2026
Merged

taciturnaxolotl merged 6 commits into
charmbracelet:mainfrom
puneet-chandna:main

Conversation

@puneet-chandna

Copy link
Copy Markdown
Contributor

This PR fixes issue #506 where Bubble Tea programs running under Wish's bubbletea.Middleware with ssh.AllocatePty() would not properly release the alternate screen when executing child processes via tea.Exec.

Problem

When using tea.Exec with wish.Command, the child process output appeared "stacked" below the TUI instead of replacing it. This happened because wish.Cmd's SetStdin, [SetStdout](cci:1: wish/cmd.go:76:0-79:1), and SetStderr methods were no-ops, causing Bubble Tea's alt screen escape sequences to be written to a different output path than the child process.

Solution

Modified wish.Cmd to properly store and use the I/O handles set by Bubble Tea's exec flow:

  • Added stdin, stdout, stderr fields to [Cmd](cci:2 /wish/cmd.go:31:0-37:1) struct
  • Implemented [SetStdin](cci:1 /wish/cmd.go:71:0-74:1), [SetStdout](cci: wish/cmd.go:76:0-79:1), SetStderr to store values
  • Updated [doRun()](cci: wish/cmd_unix.go:7:0-34:1) to use custom I/O when set, falling back to PTY otherwise

Changes

  • [cmd.go](cci:7: /wish/cmd.go:0:0-0:0) - Added stdio fields and implemented Set* methods
  • [cmd_unix.go](cci:7: wish/cmd_unix.go:0:0-0:0) - Use custom I/O when any handle is set
  • cmd_windows.go - Use custom I/O when ALL handles are set (no ppty.Slave on Windows)
  • [cmd_test.go](cci:7: wish/cmd_test.go:0:0-0:0) - Added [TestCommandSetStdio](cci:1: wish/cmd_test.go:100:0-138:1) to verify the fix

Testing

  • All existing tests pass
  • Added new test [TestCommandSetStdio](cci:1: wish/cmd_test.go:100:0-138:1) that verifies custom I/O handles are used

Before/After

Before: Child process output appears below TUI frame; scrollback shows "stacked" output
After: Child process replaces TUI cleanly; alt screen properly exits/enters around exec

Fixes #506

@puneet-chandna
puneet-chandna requested a review from a team as a code owner December 16, 2025 12:42
@puneet-chandna
puneet-chandna requested review from aymanbagabas and kujtimiihoxha and removed request for a team December 16, 2025 12:42
@codecov

codecov Bot commented Dec 16, 2025 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.78%. Comparing base (ee243c6) to head (7f3a7fb).
⚠️ Report is 290 commits behind head on main.

Files with missing lines Patch % Lines
cmd.go 66.66% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #522      +/-   ##
==========================================
- Coverage   77.23%   73.78%   -3.45%     
==========================================
  Files          19       21       +2     
  Lines         940      946       +6     
==========================================
- Hits          726      698      -28     
- Misses        160      180      +20     
- Partials       54       68      +14     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aymanbagabas aymanbagabas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, this looks good! Could you look at our x/xpty library and use it to spawn the process instead?

@puneet-chandna

Copy link
Copy Markdown
Contributor Author

@aymanbagabas Thanks for the suggestion! I’ve updated the implementation to use the x/xpty library for spawning the process. Please let me know if this matches what you had in mind.

@puneet-chandna

Copy link
Copy Markdown
Contributor Author

Hi! Just a gentle follow-up on this PR. I’ve addressed the requested changes (switched process spawning to x/xpty) and CI is passing. Whenever you have time, I’d appreciate a re-review. Thanks!

@aymanbagabas
aymanbagabas requested a review from caarlos0 January 13, 2026 22:01

@aymanbagabas aymanbagabas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Could you back port this to the v2-exp branch in a separate PR?

@puneet-chandna

Copy link
Copy Markdown
Contributor Author

I’ve opened a backport PR targeting the v2-exp branch with the same changes. Thanks!

…lity

Previously, wish.Cmd's Set* methods were no-ops, causing alt screen
escape sequences to be out of sync with child process output when
using tea.Exec with bubbletea.Middleware.

Now wish.Cmd stores and uses custom I/O handles when set by tea.Exec,
ensuring proper sequencing of alt screen commands.

Fixes charmbracelet#506
- Use xpty.WaitProcess on Windows for proper ConPTY waiting
- Implement SetStdin/SetStdout/SetStderr to store and use custom I/O
- Require all stdio to be set for custom path (avoids PTY deadlocks)

Fixes charmbracelet#506
@taciturnaxolotl

Copy link
Copy Markdown
Member

this looks awesome! thank you for fixing this

@taciturnaxolotl
taciturnaxolotl force-pushed the main branch 2 times, most recently from 7816ef4 to 9556373 Compare July 30, 2026 16:56
The all-three-set check was copy-pasted across cmd_unix.go and
cmd_windows.go with diverging comments (one claimed "any handle", the
other justified the gate via "no ppty.Slave on Windows"). The policy is
identical on both platforms and cannot be otherwise: ppty.Start
overwrites all of Stdin/Stdout/Stderr with the PTY slave, so custom
handles are necessarily all-or-nothing.

Move the gate into Run as hasCustomStdio, leaving doRun with only the
platform-specific start/wait logic, and make the no-PTY branch honor
partially set handles with nil-checked fallback to the session so the
setter contract holds there too.
xpty.WaitProcess was called with context.Background(), so a hung child
on Windows had no cancellation path. Pass c.sess.Context() instead.

On the error shape: WaitProcess returns os.Process.Wait's error, which
is *exec.ExitError on non-zero exit, the same concrete type cmd.Wait
yields on unix, so passing it through unwrapped keeps both platforms
consistent. Requires xpty v0.1.4: earlier versions swallowed context
cancellation and did not report non-zero exits on Windows.
TestCommandSetStdio used echo with an empty stdin reader, so it only
proved stdout redirection. Switch to a cat round trip: output can only
appear in the custom buffer if both stdin and stdout are wired through.

Add TestCommandSetStdioPartial asserting that setting only stdout keeps
the PTY wiring: if the all-or-nothing gate fired on a partial set, the
output would land in the custom sink instead of the session.

Also fix the comment on runEcho's nil-storm calls to say what they
actually exercise: with all handles nil the gate stays closed.
v0.1.4 fixes WaitProcess to report context cancellation and to return
*exec.ExitError for non-zero exits on Windows.
@taciturnaxolotl
taciturnaxolotl merged commit 6d395e5 into charmbracelet:main Jul 30, 2026
10 of 11 checks passed
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.

Wish Bubble Tea middleware: ExecProcess/PTY can’t replace TUI (alt screen re-enters automatically)

3 participants

⌂ 换网址