You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ProcessGroup.Adopt(process) places an already-running external process — one the group did not start (created by other code, inherited from another layer, or located by pid) — into the container, so from then on it obeys the same whole-tree rules as a process started with StartAsync: kill-on-dispose, and participation in Signal/Suspend/Resume/Members/MembersInfo/Stats and any resource limits. This restores the "kill the whole tree" guarantee for a wrapper whose child was launched outside ProcessKit. It takes a System.Diagnostics.Process (not a bare pid) on purpose: a live Process holds an open OS handle that on Windows pins the pid, so the adopt cannot race a number-recycle onto a stranger. Windows assigns the process to the Job Object (with or without limits); Linux cgroup v2 writes the pid into the group's cgroup.procs (only on a group created with resource limits — the plain, limit-free POSIX process-group mechanism, on macOS/BSD or Linux without limits, cannot relocate a foreign process and returns a typed ProcessError.Unsupported, never a silent no-op). An already-exited/nonexistent target, a missing-rights failure, or a process already in an incompatible Job returns the new typed ProcessError.Adopt (never a fabricated success). The adopted process is not our child, so ProcessKit contains and kills it purely through the OS primitive (KILL_ON_JOB_CLOSE / cgroup.kill) and never waitpids or signals its process group — the caller keeps their own Process as the exit-observation path.
Command.KillOnParentDeath() (and the pipe-friendly Command.killOnParentDeath) opts a child in to being reaped when its parent process dies suddenly — a SIGKILL, a crash, or a Windows TerminateProcess — the one case the deterministic kill-on-dispose guarantee cannot cover, because no Dispose/finalizer runs. The paired Command.KillOnParentDeathScope() reports what the current platform actually guarantees — fixed per platform and independent of whether the verb was set, an honest report like ProcessGroup.Mechanism: Windows reaps the whole tree already with no opt-in (the Job Object's KILL_ON_JOB_CLOSE fires when the kernel closes the dead parent's last Job handle) → KillOnParentDeathScope.WholeTree; Linux arms PR_SET_PDEATHSIG(SIGKILL) on the direct child via the setpriv --pdeathsig helper — a grandchild is not covered (the parent-death signal is not inherited across a fork) and the kernel resets it across an execve of a set-uid/set-gid image — → KillOnParentDeathScope.DirectChildOnly; macOS/BSD have no PR_SET_PDEATHSIG analog, so a set value fails the spawn with a typed ProcessError.Unsupported, never a silent no-op → KillOnParentDeathScope.Nothing. Where setpriv is absent on Linux the spawn fails with a typed ProcessError.Spawn naming the helper.
ProcessGroup.MembersInfo() returns an enriched, point-in-time snapshot of the group's members — the same pids Members() reports, in the same platform matrix — each as a MemberInfo carrying its parent pid (Ppid), executable image name (ExeName), and OS-reported start time (StartTime) where the platform can honestly report them. Pid is always present; every enriching field is an option and is None where the platform cannot supply it — never a fabricated value. A member that exits between enumeration and its metadata read is omitted rather than filled with invented fields, and the member's command line and environment are never included on any platform (argv routinely carries secrets; the same exclusion the logging/tracing/metrics paths enforce). Windows reads one system process snapshot for the parent pid + image name; Linux reads /proc, macOS proc_pidinfo; a BSD other than macOS reports just the pid and a best-effort start time.
Command.ResolveProgram() and CliClient.ResolveProgram() resolve a command's program to a full path without spawning it — a synchronous, side-effect-free preflight ("will this command find its program?") — against the effective childPATH: the command's own Env/EnvRemove/EnvClear (a PATH override) applied, with its PreferLocal directories consulted first, exactly as the real spawn resolves. They reuse the samePATH/PATHEXT/executable-bit resolver the launch path itself goes through (no second copy), so on a miss they return the identical typed ProcessError.NotFound — with the identical Searched diagnostic — a real run of the same command would fail with. This is distinct from Exec.which / CliClient.EnsureAvailableAsync, which resolve against the current process'sPATH (no prefer-local) — the host-wide "is this tool installed" check; Exec.which's behaviour is unchanged.
Command.PreferLocal(directory) (and the pipe-friendly Command.preferLocal) adds a directory to a priority search list consulted beforePATH when resolving a bare-name program — the way you reach for a project-local tool (node_modules/.bin, .venv/bin, tools/, a binary next to the solution) over a global one of the same name, without hand-building the path and losing cross-platform executable resolution. Directories are searched in the order added, then PATH, each through the samePATHEXT-aware (Windows) / executable-bit (POSIX) probe the PATH walk uses — so a Windows .cmd/.bat shim resolves and launches through cmd.exe /d /c exactly as on PATH, and a non-executable POSIX file is skipped just the same. A prefer-local match is always handed to the OS as its resolved absolute path, whatever its extension (the OS never searches these directories itself). A relative directory resolves against the command's CurrentDir when set (else the process's current directory); only a bare name is affected (a path-form program ignores prefer-local, as it ignores PATH), and Exec.which is unchanged (prefer-local is a launch concern, not a preflight one).
Fixed
On Windows, a bare-name program whose only PATH match carries a non-.exe extension — the .cmd/.bat shims npm, yarn, az, and many dotnet-tool wrappers install — now launches instead of failing with ProcessError.NotFound. Previously Exec.which located such a shim (its PATHEXT-aware lookup finds any extension) but the actual spawn handed the bare name to the OS, whose own search appends only .exe, so the run couldn't reach it — preflight and launch disagreed. The spawn now substitutes the resolved absolute path (the same one which reports) and routes a .cmd/.bat through cmd.exe /d /c, quoting its arguments for cmd.exe's own grammar so a metacharacter (&, |, <, >, ", …) is delivered literally rather than executed (the "BatBadBut" class, CVE-2024-24576); an argument cmd.exe cannot escape at all (a %, !, or newline) is refused with a typed ProcessError.Spawn instead of an unsafe launch. A .exe match, a path-form program, and all POSIX behaviour are unchanged.
Supervisor.LivenessHttp and Supervisor.LivenessCheck now clamp zero or negative probe intervals to