# Interactive job control

Available in Mix 0.84.0.

Interactive Mix shell commands use one process group per job, including all
pipeline stages. The shell transfers its controlling terminal to foreground
jobs and reclaims it on completion or suspension. `jobs` lists tracked jobs;
`fg N` foregrounds and continues job N; `bg N` continues it without terminal
ownership. Omitting N selects the most recent live job. A background pipeline
returns to the prompt after launch, without waiting for its stages to finish.
`jobs` includes a session-local launch command ID allocated before spawning,
distinct from its job-table ID, and displays arguments after glob expansion.
The `&` notification prints the last pipeline member's PID.

Job management is selected by the interactive entry point, and requires tty
stdin plus a controlling terminal. Setup errors produce one warning and
start the shell with noninteractive execution policy. Foreground admission
is bounded if an orphaned process group cannot stop. The shutdown mode
snapshot is seeded after cold-start terminal repair, before the monitor
starts handling HUP. Declined admission does not take the parent's terminal.
Scripts, `-c` (including SSH commands),
serve mode, redirected stdin and sessions without a controlling terminal
retain their noninteractive launch policy. Captured `run_argv` and
`run_pipeline` retain their separate capture/cancellation process groups.
Noninteractive `a | b &` still waits for upstream `a` before returning;
changing that legacy behaviour is deferred by design. Each managed command
also pays for a fork and an extra exec of the Mix trampoline before target
exec; pipelines pay that overhead per stage.
The shell's job-control signal handlers reset on exec. SIGTTOU is blocked
only around terminal handoff and restoration, so captured children do not
inherit shell-only ignored signals or a handoff-time signal mask.

Sourced shell input in an interactive shell shares its job controller. A
stopped foreground command inside source currently waits for external
continuation: returning to a restricted prompt while preserving evaluation
requires the later async host seam. `run_stream` and evaluator-owned legacy
shell/substitution paths are not integrated in this round. Bus publication,
remote admission and request cancellation are separate stages.
During an unmanaged inherited-stdio wait, Ctrl+Z stops Mix itself with its
child so an outer shell can `fg` the group. The shell suppresses SIGTSTP
only while a managed job holds the foreground terminal. A restricted
job-management prompt for suspended evaluation remains deferred.
SIGTSTP and SIGTTIN use an atomic one-shot disposition reset and reinstall
the known handler after resume. SIGTTIN always takes the default stop path,
so background terminal reads stop the shell rather than spinning on EINTR.
The managed-foreground flag changes only after successful terminal transfers;
a failed reclaim leaves it set.

From Mix 0.109.2, a foreground job reaped during SIGHUP shutdown cannot
return to another prompt. The evaluator waits for the job monitor's exit
instead of re-entering the editor and overwriting restored terminal modes.
The monitor owns the final restoration, owned-child sweep and exit status 129.

On normal shell exit or SIGHUP, Mix sends HUP followed by CONT to its owned
live jobs and allows 500 ms for exit/reaping. Survivors are reported; there
is no forced-kill escalation or guarantee about deliberately detached
descendants. Foreground terminal modes are restored even if a child exits
leaving raw mode enabled. Stopped jobs retain their own modes for `fg`.
Fresh jobs that exit normally retain cooked-mode changes (ICANON and ISIG still enabled), so
commands such as `stty tostop` remain effective. Stops, signal exits and raw
mode leakage restore the saved shell baseline. A resumed stopped job also
restores that baseline when it completes. Mix cannot infer whether an
arbitrary cooked-mode change was deliberate; this is the explicit policy.

The launch barrier runs in a private Mix trampoline **after** its first exec.
It executes `/proc/self/exe`, so open shells keep launching commands after
an installation replaces or unlinks their original executable. The fleet is
Linux; if `/proc/self/exe` is absent, or on another platform, launches fall
back to the executable path resolved at interactive admission. That fallback
restores only pre-replacement behaviour, not the unlink guarantee.
This lets Rust's spawn acknowledgement complete before the barrier waits.
Only after all children share their job group and the foreground terminal
has transferred does Mix release target execution. A second close-on-exec
pipe reports target-exec failures and one-byte trampoline failure reasons.
Acknowledgement waits observe member stops and have a 30-second deadline,
used only as a pathological-wedge backstop so cold or network-paged binaries
have time to launch. A target that stops itself before acknowledgement is
aborted deliberately: preserving that pre-ack job would risk wedging the
shell. This trades that narrow self-stop case for a recoverable prompt.
Failed launches reclaim the terminal first, send TERM/CONT, allow 500 ms,
then send KILL/CONT and allow another 500 ms. Survivors are reported and
remain registered for eventual reaping; an uninterruptible child cannot
hold the prompt indefinitely.
The survivor diagnostic in `abort_launch` is deliberately printed under the
jobs-state lock; this is accepted because the terminal has already been
reclaimed before abort cleanup.

The process monitor is the sole consumer of registered child statuses,
including stopped/continued states. SIGCHLD wakes it independently of REPL
input or evaluator progress. It never waits for arbitrary child PIDs.
`process_alive(pid)` requires a positive whole-number PID. It may reap an
exited unmanaged child (including a legacy `spawn` child), but only probes
controller-owned job PIDs with signal 0. Registered native-task PIDs share
that no-reap ownership seam from Mix 0.109.1, including pending cleanup after
an Unknown report. A managed zombie can briefly report alive until its owner
reaps it; the builtin never steals its status after registration. Task admission
is published after registration; this does not claim protection before the
task's initial exec.
The internal no-reap markers are reference counted so retirement after a
numeric PID is reused cannot erase a newer owner's registration. Each owner
balances its own registration; this does not prevent kernel PID reuse.
Audio-watch cleanup retires its marker exactly once: the worker or destructor
that takes and reaps the child owns that retirement.
Signal 0 returning `EPERM` counts as alive, including for another user's
process. This reports existence, not permission to signal that process.
