Skip to content

fix: wt create — reclaim terminal foreground after init phase#20

Merged
sahil87 merged 4 commits into
mainfrom
260602-z4p7-wt-reclaim-tty-foreground-after-init
Jun 2, 2026
Merged

fix: wt create — reclaim terminal foreground after init phase#20
sahil87 merged 4 commits into
mainfrom
260602-z4p7-wt-reclaim-tty-foreground-after-init

Conversation

@sahil87
Copy link
Copy Markdown
Owner

@sahil87 sahil87 commented Jun 2, 2026

Meta

ID Type Confidence Plan Review
z4p7 fix 5.0/5.0 7/7 tasks, 0/15 acceptance ✓ 1 cycle

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Impact: +454/−1 code (excluding fab/, docs/) · +1087/−4 total

Summary

wt create acts as a job-control parent for the init child (Setpgid: true) while sharing its controlling terminal, but never reclaimed terminal foreground after the child returned. If the init script (or any descendant — e.g. a zsh -i direnv-hook check) grabbed the terminal foreground and exited without restoring it, wt was left in the background, so the next TTY write (the Open-phase menu render) tripped SIGTTOU and suspended the process (zsh: suspended (tty output)), stranding a half-finished worktree. This fix makes wt capture-before and reclaim-after terminal foreground around the init child on every exit path, restoring reliable interactive behavior (Constitution VI).

Changes

  • Save/restore terminal foreground around the init phase (wt create) — capture tcgetpgrp before the init child, tcsetpgrp back to wt's own pgrp after on all exit paths (success/pre-Open, init-failure/pre-banner, SIGINT-abort), wrapped in signal.Ignore(SIGTTOU).
  • Unix/Windows split for the tcsetpgrp helpers — new tty_unix.go (x/sys/unix ioctls TIOCGPGRP/TIOCSPGRP) and tty_windows.go (no-op stubs), mirroring the existing signal_unix.go/signal_windows.go pattern.
  • Guard on TTY presence — all foreground bookkeeping is a no-op when stdin is not a real terminal (--non-interactive, piped, CI); no error, no warning.
  • Preserve the SIGINT-during-init Option B contract — Setpgid: true, the tight reinstall window, and defer rb.Execute() are unchanged; the reclaim only touches the terminal's foreground, never the child's pgrp.
  • Apply the same reclaim to wt init — standalone interactive wt init is no longer left suspended after the child strands foreground.
  • PTY integration test asserting no suspension — Unix-only, self-skipping, in-tree openpty on x/sys/unix; verified to catch the regression (exit 20 / WIFSTOPPED without the fix, exit 0 with it).

sahil87 added 2 commits June 2, 2026 17:14
… menu)

wt create (and wt init) lost terminal foreground during the init phase: an
interactive child in an init script (e.g. zsh -i in a direnv-hook check) grabs
the controlling terminal's foreground group and exits without restoring it,
orphaning wt to the background so the Open-phase menu's TTY write triggers
SIGTTOU (zsh: suspended (tty output)).

Fix: wt now captures the terminal foreground before the init child and reclaims
it (tcsetpgrp back to its own pgrp, wrapped in signal.Ignore(SIGTTOU)) after the
child returns on all exit paths, gated on term.IsTerminal (no-op when not a TTY
/ --non-interactive / CI), no-op on Windows. Preserves the SIGINT-during-init
Option B contract (Setpgid: true unchanged). Includes a self-skipping Unix PTY
integration test verified to catch the regression. No CLI surface change; ioctls
via golang.org/x/sys/unix (promoted indirect to direct, no new module).
@sahil87 sahil87 marked this pull request as ready for review June 2, 2026 11:47
@sahil-noon sahil-noon requested a review from Copilot June 2, 2026 12:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a job-control correctness issue where wt create/wt init could be left in a background process group after the init child exits, causing subsequent terminal operations (notably the Open/menu raw-mode transition) to trigger SIGTTOU and stop the process. The PR introduces TTY foreground capture/reclaim around the init phase (guarded by TTY detection), provides Unix/Windows helper implementations, and adds an end-to-end PTY-based regression test.

Changes:

  • Add Unix/Windows build-tagged helpers to tcgetpgrp/tcsetpgrp the controlling terminal foreground process group, ignoring SIGTTOU during the reclaim.
  • Apply foreground capture/reclaim around the init child in both wt create and wt init, including explicit reclaims on success/failure paths.
  • Add a Unix-only, self-skipping PTY integration test that reproduces the “foreground stranded → SIGTTOU stop” regression.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/go.mod Promotes golang.org/x/sys to a direct dependency for Unix ioctl helpers/tests.
src/cmd/wt/tty_unix.go Implements terminalForeground/reclaimTerminalForeground via x/sys/unix ioctls with SIGTTOU ignored during reclaim.
src/cmd/wt/tty_windows.go Windows no-op stubs for foreground bookkeeping (no tcsetpgrp model).
src/cmd/wt/create.go Captures/restores terminal foreground around the init child; reclaims before failure banner and before Open/menu.
src/cmd/wt/init.go Applies the same capture/reclaim behavior to standalone wt init around cmd.Run().
src/cmd/wt/tty_pty_test.go Adds an end-to-end PTY regression test using helper binaries to reproduce and detect WIFSTOPPED behavior.
fab/changes/260602-z4p7-wt-reclaim-tty-foreground-after-init/plan.md Change plan/requirements documentation for the fix and test strategy.
fab/changes/260602-z4p7-wt-reclaim-tty-foreground-after-init/intake.md Intake/root-cause writeup documenting the SIGTTOU stop scenario and chosen approach.
fab/changes/260602-z4p7-wt-reclaim-tty-foreground-after-init/.status.yaml FAB status metadata for the change lifecycle.
fab/changes/260602-z4p7-wt-reclaim-tty-foreground-after-init/.history.jsonl FAB history log for stage transitions/commands.
docs/memory/wt-cli/init-failure-contract.md Documents the new “reclaim terminal foreground after init” contract alongside SIGINT Option B invariants.
docs/memory/wt-cli/create-output-phases.md Notes the pre-Open foreground reclaim ordering guarantee in the output-phases contract.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +26
1. `PhaseSeparator("Git")` — emitted at `create.go:264` immediately before the deferred summary block (`Created worktree:` / `Path:` / `Branch:`), joining the existing deferred-summary emission under the rollback handler.
2. The **Init** separator — emitted by the init runner, NOT by `create.go` (see next requirement).
3. `PhaseSeparator("Open")` — emitted at `create.go:319` immediately before the open phase.
3. `PhaseSeparator("Open")` — emitted immediately before the open phase. As of `260602-z4p7`, when the init phase ran, the Open separator + menu render are now preceded by an **unconditional foreground reclaim** (`reclaimTerminalForeground(ttyFd, wtPgid)` at `create.go:361–363`, gated on the same `term.IsTerminal` check as capture) so the Open phase can never SIGTTOU on a shared-TTY init child that stranded terminal foreground. The separator output contract itself is unchanged — this is a job-control ordering guarantee, not an output change. See `init-failure-contract.md` "Terminal foreground is reclaimed after the init child".
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed — replaced the stale create.go line numbers (264/282, now 265/326 after this PR) with descriptive references ("before the Git/Open separator", "before the init-phase signal.Reset") so the contract no longer drifts with line shifts. (1be5d87)

Comment thread src/cmd/wt/tty_pty_test.go Outdated
// wt is either still alive (not stopped) or has exited cleanly.
// We poll for up to a few seconds: any STOPPED observation => bug (exit 20);
// a clean exit or a still-running-but-not-stopped wt => fix (exit 0).
deadline := time.Now().Add(8 * time.Second)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed — bumped the launcher success-poll window from 8s to 15s (matching integration_sigint_unix_test.go) and documented why it must exceed wt's time-to-Open-phase, so a slow runner can't elapse the deadline before wt reaches term.MakeRaw and report a vacuous pass. (1be5d87)

@sahil87 sahil87 merged commit 9c8148c into main Jun 2, 2026
2 checks passed
@sahil87 sahil87 deleted the 260602-z4p7-wt-reclaim-tty-foreground-after-init branch June 2, 2026 13:08
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.

2 participants