Skip to content

fix(macos): exec claude directly when stdin is a tty - #5

Open
ron2k1 wants to merge 1 commit into
mainfrom
fix/macos-tty-foreground-exec
Open

ron2k1 wants to merge 1 commit into
mainfrom
fix/macos-tty-foreground-exec

Conversation

@ron2k1

@ron2k1 ron2k1 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Problem

Interactive claude hung on a blank screen the moment the wrapper went live. The wrapper backgrounds claude under set -m so the child gets its own process group for the watchdog to reap, but a background process group that calls tcsetattr (the TUI's raw-mode switch) draws SIGTTOU and suspends unconditionally. The wrapper then blocked in wait forever, and Ctrl+C killed the suspended child via the cleanup trap.

Why not fg

bash 3.2's non-interactive fg cannot transfer tty ownership (no tcsetpgrp access), so foregrounding the job after launch just re-suspends it in a SIGTTOU loop. Reproduced under a pty harness. Supervision and a foreground TUI are mutually exclusive in pure bash 3.2.

Fix

When stdin is a tty, exec the resolved binary before any job-control machinery. Interactive runs become byte-identical to bare claude (signal flow, exit codes). The watchdog path still covers non-tty runs (scripts, pipes, agent spawns), which is where orphan reaping actually pays rent.

Verification

  • pty harness + stub TUI performing the same tcsetattr: previously suspended forever, now runs to completion with exit code propagated through the wrapper
  • non-tty stub run: watchdog path intact, exit 0
  • non-tty real binary: --version returns 2.1.220, exit 0
  • no leftover watchdog processes after either path

🤖 Generated with Claude Code

The wrapper backgrounds claude under `set -m` so the child gets its own
process group for the watchdog to reap. But a background process group
that calls tcsetattr (the TUI's raw-mode switch) draws SIGTTOU and
suspends, so interactive `claude` hung on a blank screen while the
wrapper blocked in `wait`, and Ctrl+C then killed the suspended child
via the cleanup trap.

bash 3.2's non-interactive `fg` cannot transfer tty ownership (no
tcsetpgrp), so foregrounding after the fact just re-suspends in a loop;
supervision and a foreground TUI are mutually exclusive in pure bash.
With a tty, exec the real binary and get out of the way. The watchdog
path still covers non-tty runs (scripts, pipes, agent spawns), which is
where orphan reaping actually matters.

Verified under a pty harness: a stub TUI doing the same tcsetattr now
runs to completion with its exit code propagated; the non-tty watchdog
path and real-binary passthrough are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 participant