ProcessKit CLI exposes one command surface across Windows, Linux, macOS, and source-built BSD targets, but it does not pretend their kernel containment primitives are equivalent. Every run reports both the mechanism it obtained and what happens if the runner dies before it can execute teardown.
| Operating system | Target triple | Distribution |
|---|---|---|
| Windows x86_64 | x86_64-pc-windows-msvc |
Prebuilt release archive |
| Windows Arm64 | aarch64-pc-windows-msvc |
Prebuilt release archive |
| Linux x86_64 glibc | x86_64-unknown-linux-gnu |
Prebuilt release archive |
| Linux Arm64 glibc | aarch64-unknown-linux-gnu |
Prebuilt release archive |
| Linux x86_64 musl | x86_64-unknown-linux-musl |
Static prebuilt archive |
| Linux Arm64 musl | aarch64-unknown-linux-musl |
Static prebuilt archive |
| macOS Apple Silicon | aarch64-apple-darwin |
Prebuilt release archive |
Other Rust-supported targets may build from source, but are not part of the release-artifact or CI promise unless listed here.
run_started contains two independent fields:
mechanism: how normal teardown addresses the tree;abrupt_cleanup: what the kernel guarantees when the runner never executes normal teardown.
| Platform / obtained mechanism | mechanism |
abrupt_cleanup |
|---|---|---|
| Windows Job Object | job_object |
whole_tree |
| Linux cgroup v2 | cgroup_v2 |
direct_child_only |
| Linux process-group fallback | process_group |
direct_child_only |
| FreeBSD process reaper | process_reaper |
none |
| macOS / non-FreeBSD Unix process group | process_group |
none |
Normal completion, timeout, caught cancellation signals, and control-plane
actions still run the full owned-container teardown on every platform. The
last column applies only to a crash, SIGKILL, outer Job termination, or an
equivalent event that prevents the runner from reaching its cleanup code.
A run owns a Job Object configured for kill-on-close. Child processes and their descendants are assigned to the job, and closing the last owning handle reaps the whole job.
Properties:
- strongest abrupt-owner-death guarantee (
whole_tree); - whole-tree memory, CPU, and active-process limits;
- member snapshots through Job queries;
- atomic hard kill on timeout/cancel/kill teardown;
- best-effort graceful close before hard kill where a member exposes a window;
- opt-in
CTRL_BREAKfor console children via--windows-graceful-ctrl-break.
Modern Windows versions allow nested Job Objects when the outer job's policy permits it. A CI runner, service host, or container runtime may already place the CLI in a job. The E2E suite covers nested-job launch behavior, but an outer job remains authoritative and can terminate the runner plus its child job.
The runner does not allocate a console. A normal child inherits what the
platform would ordinarily provide. --create-no-window is an explicit
Windows-only request and conflicts with --inherit-stdio.
--windows-graceful-ctrl-break keeps the shared console, creates a child console
process group, and lets ProcessKit address that group during graceful teardown. It
therefore conflicts with --create-no-window and detached execution.
When cgroup v2 is available and delegated, ProcessKit creates a run cgroup and moves the child tree into it. Normal hard teardown addresses all members in the cgroup.
If controller/delegation requirements are not met, an unrestricted run may
fall back to a POSIX process group and reports process_group. A run that
explicitly requests resource limits fails instead, because falling back would
discard a required policy.
Linux parent-death signaling kills the direct child if the runner dies
abruptly. The cgroup itself persists and does not automatically kill every
grandchild, hence direct_child_only rather than whole_tree.
On FreeBSD, ProcessKit 3.3 uses the kernel's procctl(2) process reaper.
Normal teardown and member discovery cover the whole reaper tree, including
descendants that call setsid or double-fork. This is stronger than the POSIX
process-group fallback for membership and kill scope, so it reports
process_reaper rather than process_group.
FreeBSD does not provide the resource-limit or statistics primitives used by
ProcessKit's other backends. A run that requests a resource cap fails before
spawn with the normal limit_hit/backend-error sequence; an unrestricted run
reports resource_summary with all measurements null and
read_error: false. Parent-death cleanup remains none: procctl(2) gives
normal teardown a whole-tree scope, but ProcessKit 3.3 has no supported
owner-death primitive for this path.
The runner uses a POSIX process group. Normal teardown signals and kills the group, covering ordinary descendants that remain in it.
Limitations:
- a descendant can deliberately escape with
setsid/ double-fork; - the backend cannot provide whole-tree resource limits;
- a just-exited member may still appear briefly during diagnostics;
- no portable owner-death primitive reaps the group after an uncatchable runner
death, so
abrupt_cleanupisnone.
The mechanism field exists so an adapter can reject this weaker contract when its workload requires stronger containment.
| Capability | Windows Job | Linux cgroup v2 | FreeBSD process reaper | POSIX process group |
|---|---|---|---|---|
| Normal whole-tree hard teardown | Yes | Yes | Yes | Group members only |
| Whole-tree abrupt runner-death reap | Yes | No | No | No |
| Enriched member snapshots | Yes | Yes | Backend-dependent | Backend-dependent |
| Memory limit | Yes | With controller access | No | No |
| Process-count limit | Yes | With controller access | No | No |
| CPU quota | Yes | With controller access | No | No |
| Soft-stop request | Window close plus opt-in console CTRL_BREAK |
SIGTERM |
Whole-tree hard-stop | SIGTERM |
| Direct inherited terminal | Yes | Yes | Yes | Yes |
| PTY emulation | No | No | No | No |
The repository's GitHub Actions matrix builds and tests Windows, Linux, and macOS, including Arm runners where available. FreeBSD is source-build-only and is not currently a CI or release-artifact target. The opt-in E2E tier drives the built binary through real containment scenarios: leaked descendants, nonzero roots, abrupt runner death, nested Windows jobs, PID reuse, real console/terminal I/O, and cancellation.
That matrix proves the repository's release contract; it does not prove that a particular Linux deployment grants cgroup controllers. Test mechanism selection and limit application in the actual service/container environment.
Everything above is what a platform can give. Which of it this particular host
actually gives is a question about the host — cgroup delegation may be absent, a
registry directory may be unwritable, a local endpoint may not bind — and
doctor answers it by running a bounded scratch containment and reporting what it
observed:
processkit-cli doctor --jsonIts containment.mechanism and containment.abrupt_cleanup are the same two fields
this page's first table lists, read off a real run on this host rather than inferred
from the platform; --check-resource-controller additionally reports whether the
limit rows of the capability matrix are available here. It is the setup-time
counterpart to the run-time acceptance policy below, and unlike probe it proves the
containment path end to end — see docs/troubleshooting.md,
"Qualifying a host: doctor".
An adapter can read the first run_started event and fail closed:
require mechanism == job_object or cgroup_v2
require abrupt_cleanup == whole_tree # Windows-only today
The same two policies can be applied one step earlier, to the host rather than to a run, with the same vocabulary and an exit code instead of a field to compare:
processkit-cli doctor --require-mechanism cgroup_v2
processkit-cli doctor --require-abrupt-cleanup whole_treeEach is an exact match against what this host reports — deliberately not an "at least
this strong" comparison, because the three abrupt_cleanup levels are platform facts
and this project publishes no ordering between them. An unmet requirement exits
HOST_UNQUALIFIED (116) and still prints the full report.
These are separate policies. A Linux cgroup gives strong normal teardown but not Windows-equivalent abrupt cleanup.
- Running in containers.
- Resource limits.
- Timeouts and cancellation.
- JSONL event schema.
- Troubleshooting — qualifying a
host with
doctor, and reading a negative verdict.