Skip to content

fix: prevent tmux detach from stalling Happy CLI#539

Open
dzlobin wants to merge 1 commit intoslopus:mainfrom
dzlobin:fix/533-tmux-detach-stall
Open

fix: prevent tmux detach from stalling Happy CLI#539
dzlobin wants to merge 1 commit intoslopus:mainfrom
dzlobin:fix/533-tmux-detach-stall

Conversation

@dzlobin
Copy link

@dzlobin dzlobin commented Feb 4, 2026

Summary

  • Wraps Ink's stdout in a non-blocking proxy that drops writes when PTY backpressure is detected (e.g. tmux detach), preventing the Node.js event loop from blocking
  • Applied consistently across Claude, Codex, and Gemini agent launchers
  • Includes 12 unit tests covering normal writes, backpressure detection, drain recovery, and listener accumulation prevention

Root Cause

When tmux detaches, the PTY buffer (~4KB on macOS) fills with no reader. process.stdout.write() then blocks synchronously, freezing the entire Node.js event loop and stalling all Happy instances in that tmux session.

Fix

A Proxy wraps process.stdout and intercepts write() calls. When writableNeedDrain is true or write() returns false, subsequent writes are silently dropped (with callbacks still invoked) until a drain event fires. This is passed to Ink's render() via the stdout option.

Closes #533

Test plan

  • 12 unit tests passing (vitest)
  • Manual testing: run happy in tmux, detach, verify no stall, reattach and confirm UI resumes
  • Test with multiple instances in same tmux session

🤖 Generated with Claude Code

When tmux detaches, the PTY buffer fills and process.stdout.write()
blocks synchronously, freezing the Node.js event loop. This wraps
Ink's stdout in a non-blocking proxy that drops writes during
backpressure instead of blocking.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
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.

Only one happy instance works reliably when detached in tmux

1 participant