`zsh -i -c '<script>'` leaves the terminal's foreground process group pointing atzsh's own (by then dead) pgrp whenever the script's last command is an external
program or a missing command. Whatever spawned zsh is silently backgrounded.
Plain writes still succeed with TOSTOP off, but the next tcsetattr() raises
SIGTTOU regardless of TOSTOP and stops the whole group.
Repro (parent = one process in its own pgrp, foreground job of an interactive
shell; probe = tcgetpgrp() on /dev/tty after zsh exits; empty ZDOTDIR):
zsh -i -c 'true' exit 0 fg pgrp restored
zsh -i -c 'false' exit 1 fg pgrp restored
zsh -i -c '/bin/true' exit 0 LEAKED
zsh -i -c 'ls /' exit 0 LEAKED
zsh -i -c 'no-such-cmd' exit 127 LEAKED
zsh -i -c 'ls /; true' exit 0 restored (external command not last)
zsh -c '/bin/true' exit 0 restored (non-interactive)
Same result on zsh 5.9.2 (Linux) and 5.9 (macOS, arm64). bash 5.3.15 restores
the foreground in every equivalent case.
This looks like the exec-instead-of-fork path for the final command (Src/exec.c,
above execpline: "last1 is a flag that this command is the last command in a
shell that is about to exit, so we can exec instead of forking"). With exec no
zsh survives to restore the foreground; command-not-found seems to exit the same
way. Interactive zsh has already tcsetpgrp()'d itself into the foreground, so the
terminal is left owned by a dead pgrp. Appending `; true` makes it fork and the
leak disappears.
Found via topgrade (a Rust updater) probing plugin managers with `zsh -i -c
'...'`: the leak backgrounds it and it gets SIGTTOU-stopped at its next prompt,
ending the script on a builtin.