fix(config): keep wt config show from suspending on the zsh compinit probe#3327
Conversation
…t probe The interactive `zsh -ic` compinit probe enables job control, puts itself in a new process group, and `tcsetpgrp`s to claim the terminal foreground. The 2s kill-on-timeout added in #3165 SIGKILLs the probe before it can restore the foreground group, so `wt` is left in a *background* process group. The pager `wt config show` spawns moments later then raises SIGTTOU on its first terminal operation, suspending the command (`suspended (tty output)`, #3322). Pass `+m` to disable job control in both probes (`check_zsh_compinit_missing` and `detect_zsh_compinit`), so the interactive shell never grabs wt's controlling terminal and a timeout-kill can't strand wt in the background. Verified at the OS level under a PTY: the unpatched probe steals the terminal foreground (`tcgetpgrp` moves to the dead probe's group and stays); with `+m` the foreground is intact and the pager runs normally. Closes #3322 Co-Authored-By: Claude <noreply@anthropic.com>
|
I see we're repeating things a bit — can we unify? if not easy, put a TODO and we'll come back to it Separately: how could we have caught this initially? A PTY test? |
Remove an accidentally-committed `-l` file (captured `wt config show` output) and cross-link the two duplicated interactive zsh compinit probes (`check_zsh_compinit_missing` / `shell::detect_zsh_compinit`) with a shared TODO. Both had to receive the #3322 `+m` job-control fix; they diverge on purpose (`--no-globalrcs`, result type, runner), so unification is deferred behind one parameterized helper rather than done mechanically here. Co-Authored-By: Claude <noreply@anthropic.com>
|
Done in 57f57ad. Repetition. The two interactive zsh compinit probes —
I left a shared Also dropped a stray Catching it initially — yes, a PTY test would. And the deterministic signal is not the downstream pager suspend (that's the env-dependent part the unit guards punt on) — it's the controlling-terminal foreground process group right after the timeout-kill. I reproduced it under a PTY, replicating the probe's exact spawn (interactive Without The repo already has the harness for it ( |
Release v0.67.0: version bump and changelog. Highlights since v0.66.0: experimental `wt remove --reap` (#3396), `wt switch -x` opening the picker (#3394), the termimad panic containment in the picker's PR-comments preview (#3408), the `wt config show` SIGTTOU fix (#3327), the `GIT_*` discovery-var scrub for `wt step for-each` and the `--execute` fallback (#3400), and the `wt list` prompt-reserve fix (#3409). Seven commits landed on `main` after the initial cut; `origin/main` is merged back in and the two user-facing ones (#3394, #3411) plus the BY CONTEXT profile table (#3403) are folded into the changelog. Pre-release validation: local pre-merge gate green on the merged tree (4389 tests); nightly cross-platform suite green on the initial cut ([run 29086956095](https://github.com/max-sixty/worktrunk/actions/runs/29086956095)), with the seven post-cut commits each validated by their own PR CI; `cargo semver-checks` reports no breaking changes; data-loss surface review of the cumulative diff (including the drift commits) found one new destructive capability (`--reap`, explicit opt-in, adjudicated acceptable). > _This was written by Claude Code on behalf of max_
Problem
After upgrading 0.56.0 → 0.63.0,
wt config showsuspends instead of running:suspended (tty output)is SIGTTOU: a process in a background process group touched the controlling terminal. The reporter'swt -vv config showshows the suspend landing right after the log-path header and before any config content — i.e. the pager (less) is being suspended on its first terminal operation.Root cause
wt config showruns an interactive zsh probe to detect whethercompinitis configured (check_zsh_compinit_missing, mirrored byshell::detect_zsh_compinit). An interactivezsh -icwith a controlling terminal enables job control: itsetpgids itself into a new process group andtcsetpgrps to claim the terminal foreground.#3165 (shipped in 0.62.0, inside the reporter's upgrade window) added a 2s kill-on-timeout to that probe. When the user's interactive rc is slow to start or blocks on a prompt (e.g. compinit's insecure-directories prompt), the probe is SIGKILLed before it restores the foreground process group. SIGKILL can't be caught, so zsh's terminal-restore-on-exit never runs — the terminal foreground is left pointing at the dead probe's group, and
wtis now a background process group. The pagerwt config showspawns moments later then raises SIGTTOU on its firsttcsetattr, suspending the command.This is environment-specific: it only fires when the probe actually times out (slow/prompting interactive rc), which is why it doesn't reproduce for everyone.
Solution
Pass
+m(disable job control) to both interactive probes. With job control off, the probe never grabswt's controlling terminal, so a timeout-kill can't strandwtin a background process group.+mdoesn't affect compinit/compdefdetection — it only disables terminal foreground management the probe never needed.Testing
Verified at the OS level under a PTY, replicating the probe's exact spawn (interactive
zsh -ic, stdin/stdout/stderr redirected, slow rc, SIGKILL on timeout):tcgetpgrpof the controlling terminal moves to the probe's process group and stays there after the kill (foreground stolen) — the next terminal op suspends with SIGTTOU.+m: the terminal foreground is intact after the kill, andwt config showreaches the pager normally.Added regression guards (
test_compinit_probe_disables_job_control,test_zsh_probe_disables_job_control) asserting both probes pass+mbefore-ic. Full terminal-suspend behavior depends on a real controlling TTY + a slow zsh rc, so it isn't unit-testable in CI without flakiness; the guards lock in the load-bearing flag.Closes #3322 — automated triage