1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
[]
= "processkit"
= "3.0.2"
= "2024"
= "Async child-process management for tokio: whole-tree kill-on-drop (no orphans), plus streaming, pipelines, timeouts, and supervision"
= "MIT"
= "https://github.com/ZelAnton/ProcessKit-rs"
= "https://docs.rs/processkit"
= "https://github.com/ZelAnton/ProcessKit-rs"
= "README.md"
# Up to 5 keywords (≤20 chars each): high-volume terms people actually search,
# not niche mechanism names (job-object/cgroup live in the description instead).
= ["process", "subprocess", "tokio", "async", "process-group"]
# Up to 5 *valid* crates.io category slugs (https://crates.io/category_slugs).
# Was mis-tagged `command-line-utilities` — that slug is for CLI *binaries*, not
# a library; replaced with the OS-API subcategories matching the cgroup /
# Job Object / process-group machinery, plus `concurrency`.
= [
"asynchronous",
"os",
"os::unix-apis",
"os::windows-apis",
"concurrency",
]
# Keep the published crate lean: ship code, docs, and license — not the
# dev/CI/agent tooling that only matters in the repo. (.claude/, ideas/, and
# decisions/ aren't listed here: they're untracked — see the global gitignore —
# so cargo's git-tracked-files package source never sees them regardless.)
= [
".github/",
".editorconfig",
".gitattributes",
"AGENTS.md",
"CLAUDE.md",
"cliff.toml",
"deny.toml",
"rust-toolchain.toml",
# cargo-fuzz harness (nightly-only, its own Cargo.toml/Cargo.lock) — dev
# tooling that never ships in the published crate; see fuzz/Cargo.toml and
# the fuzzing-only entry point it drives, `pump::fuzz_decode_pump_lines`
# (`cfg(fuzzing)`, src/pump.rs).
"fuzz/",
# loom model-check harness (its own Cargo.toml/Cargo.lock, built only by the
# `loom` CI job under `--cfg loom`) — dev tooling that `#[path]`-includes the
# PID-lifecycle cores from `src/`; never ships in the published crate. See
# loom/README.md.
"loom/",
# 3.5 MB README banner — rendered from its absolute raw.githubusercontent URL,
# so it never needs to ship inside the published crate.
"cover.png",
# mdBook config + theme + nav index for the GitHub Pages docs site — only
# matters for building that site, not for a consumer of the published crate.
"book.toml",
"theme/",
"docs/SUMMARY.md",
"docs/.nojekyll",
# ~4.5 MB docs-site cover images (same reasoning as cover.png above).
"docs/processkit-cover.png",
"docs/processkit-cover-social.jpg",
]
# Minimum Supported Rust Version. 1.88 is required for `let`-chains
# (`if let … && let …`), used in the unix process-group/cgroup code. The `msrv`
# CI job verifies it — keep the two in sync; raise both when adopting a newer feature.
= "1.88"
# Build docs.rs with every feature so the optional surfaces — `limits`
# (ResourceLimits), the `mock` MockRunner, the `tracing` integration — are
# documented (default features alone hide them).
[]
= true
# `--cfg docsrs` activates `feature(doc_cfg)` (see src/lib.rs) so the rendered
# docs carry per-item "Available on crate feature `X`" badges.
= ["--cfg", "docsrs"]
# Every public type must be `Debug` (consumers derive Debug on structs holding
# ours); warn-level here becomes a hard error under CI's `-D warnings`.
[]
= "warn"
# All `unsafe` lives in explicit `unsafe {}` blocks inside safe fns (the FFI in
# `sys/`); there are no `unsafe fn` today. Deny the day someone adds one and
# forgets to keep that discipline. (`missing_docs` is enforced lib-scoped in
# `src/lib.rs` so it doesn't burden examples/tests.)
= "deny"
# Neither `cfg(fuzzing)` nor `cfg(loom)` is a Cargo feature; both are set via
# `RUSTFLAGS` for a whole build and gate testing-only code paths:
# * `cfg(fuzzing)` — `cargo fuzz build` sets it for every crate in the graph so
# `fuzz/fuzz_targets/*.rs` can call a fuzzing-only entry point (see
# `src/pump.rs`'s `fuzz_decode_pump_lines`) without it ever appearing in an
# ordinary build's public API.
# * `cfg(loom)` — the PID-lifecycle lock-free cores (`sys/pid_gate.rs`,
# `running/deadline.rs`, `sys/skip_drop_kill.rs`, and the `src/sync.rs` layer)
# carry `cfg(loom)`/`cfg(all(test, loom))` gates so the standalone loom harness
# (`loom/`, run by the `loom` CI job with `RUSTFLAGS=--cfg loom`) can pull those
# files in via `#[path]` and model-check them against loom's atomics/mutex — none
# of which is set in an ordinary build of this crate, but the gates are compiled
# (as `not(loom)`) here regardless. Declaring the cfg keeps `unexpected_cfgs`
# (which `-D warnings` would turn into a build failure on every normal
# `cargo build`/`clippy`) from firing on them.
# Declare both so rustc's `unexpected_cfgs` lint does not fire.
= { = "warn", = ["cfg(fuzzing)", "cfg(loom)"] }
# The most valuable pedantic-clippy slice for an error-rich crate (Rust API
# Guidelines C-FAILURE): every public fallible method must document its error
# conditions and every public method that can panic must document when. warn
# here, hard error under CI's `-D warnings`. The crate's error invariants
# (Timeout captured vs Cancelled raised, NotReady ≠ Timeout) live in the guides;
# these lints force them into the per-item rustdoc docs.rs shows first.
[]
= "warn"
= "warn"
# ---------------------------------------------------------------------------
# Dependencies — every entry carries a "why" (see AGENTS.md "Dependency
# management"). Pin major versions, enable only the features used, keep
# Cargo.lock committed.
# ---------------------------------------------------------------------------
[]
# Async runtime + child-process management: `process` spawns children,
# `time` enforces timeouts, `io-util` streams stdin/stdout line-by-line,
# `rt` powers the background stderr-drain task, `macros` for `#[tokio::main]`
# in examples, `sync` for the oneshot/Notify used in shutdown coordination,
# `fs` for the async file handle behind `Stdin::from_file`,
# `net` for the TcpStream the `wait_for_port` readiness probe connects with.
# `1.39` floor: two of our call sites need APIs stabilized well above `1.0` —
# `JoinHandle::abort_handle` (`running/mod.rs`, `pipeline.rs`; added in
# `1.27`) and `Command::as_std_mut` (`command.rs`, `sys/windows.rs`; added in
# `1.39`). Below `1.39` those fail to compile (`E0624`/`E0599`) rather than
# just missing a feature flag — this floor makes the declared minimum honest.
# (It also sidesteps an unrelated build trap: below `1.27`ish, tokio's
# declared `mio` floor can resolve down to a `mio` still depending on
# `ntapi 0.3.7`, which fails to compile under current rustc with `E0793`,
# unaligned packed-struct reference promoted to a hard error; `1.39` pulls
# `mio 1.x`, which dropped `ntapi` entirely.)
= { = "1.39", = ["process", "time", "io-util", "rt", "macros", "sync", "fs", "net"] }
# `async fn` in the object-safe `ProcessRunner` trait so it stays `dyn`-able
# and mockable (native async-in-trait isn't object-safe on our MSRV). `0.1.9`
# floor: below it, async-trait's codegen for `impl<R: Trait> Trait for
# Generic<R>` references the outer impl's generic param from the inner
# `async fn`'s desugared body, which rustc rejects (`E0401`,
# "can't use generic parameters from outer item") — our `RecordingRunner<R>`
# and `&R` blanket impls need the fixed codegen.
= "0.1.9"
# Structured `Error` carrying program / exit code / stderr / timeout instead
# of stringly-typed failures; `#[non_exhaustive]` so variants can grow.
= "2"
# Decode child stdout/stderr in non-UTF-8 legacy encodings (Shift-JIS,
# Windows-1252, GBK, …) for the per-stream encoding-override option; default
# stays UTF-8.
= "0.8"
# Adapt tokio's `Lines` reader into an `impl Stream` for the streaming-output
# helper, so callers consume stdout with the standard `Stream` combinators.
# `io-util` gates the `wrappers::LinesStream` adapter we use.
= { = "0.1.1", = ["io-util"] }
# Optional per-run observability (program/exit code/duration — deliberately
# NOT argv or env, which routinely carry secrets). Off by default; zero cost
# unless the `tracing` feature is enabled. `0.1.6` floor: below it, the
# `target: "...", field = ?value, "message"` event-macro syntax our call
# sites use isn't supported yet (`fieldset!` recursion / "no rules expected"
# macro-expansion errors).
= { = "0.1.6", = true }
# Optional metrics emission (counters/histograms over data the crate already
# computes — program/mechanism/outcome/exit code/duration, deliberately NOT argv
# or env, which routinely carry secrets). Off by default; zero cost unless the
# `metrics` feature is enabled. A thin façade like `tracing`: the crate emits into
# whatever global recorder the consumer installs (a Prometheus/OTel exporter),
# pulling no exporter of its own. `0.24` floor: the `counter!`/`histogram!` macros
# and the `Recorder`/`set_global_recorder` API the emission layer and its
# secret-hygiene test use are the current 0.23+-line shape; pin the live major.
= { = "0.24.6", = true }
# Optional auto-generated `MockRunner` for downstream tests. Test-only; pulled
# in solely behind the `mock` feature, never in production builds.
= { = "0.15", = true }
# `CancellationToken` for first-class run cancellation (core, not optional):
# structured-concurrency cancellation that tears a run's tree down. The `sync`
# module (where CancellationToken lives) is always compiled in tokio-util — no
# inner feature needed; default-features = false pulls in nothing else.
= { = "0.7", = false }
# Optional serialization for the `record` feature's JSON cassettes
# (RecordReplayRunner): `derive` for the cassette structs, serde_json for the
# human-diffable on-disk format. Never in production builds without `record`.
= { = "1.0.166", = ["derive"], = true }
= { = "1.0.100", = true }
# `#[mutants::skip]`, marking a handful of genuinely equivalent-mutant helpers
# (`buffer::clamp_dropoldest_tail`, `pump::drain_consumed_prefix`) as out of
# scope for the `mutants` CI tier (.cargo/mutants.toml). A tiny, no-op-at-
# compile-time attribute macro (see its own doc comment) — the annotated
# functions aren't feature-gated, so this is a plain, unconditional dependency
# rather than optional.
= "0.0.4"
[]
# Feature flags are *visibility* gates: additive, hiding API without changing the
# core's semantics — the kill-on-drop tree guarantee is unconditional in every
# configuration. (A broader visibility split was tried and deliberately rolled
# back — decision recorded in decisions/architecture-audit-2026-06.md; full
# analysis in git history, ideas/three-layer-resource-split.md.)
#
# The default is lean: `process-control` (pure code, no extra deps) is the one
# capability most process-management callers reach for. `stats` is *opt-in* — it
# carries a real build cost (on Windows a `windows-sys` FFI feature) and a
# specialized purpose (metrics), and the crate's core (spawn / contain / capture /
# stream / pipeline) never needs it. Enable it with `features = ["stats"]` (or
# `limits`, which implies it).
= ["process-control"]
# Resource measurement: `ProcessGroupStats`, `ProcessGroup::stats`, the per-process
# `RunningProcess::cpu_time`/`peak_memory_bytes` diagnostics, and `RunProfile` /
# `RunningProcess::profile`. Opt-in: on Windows it enables a `windows-sys` FFI
# feature (the ProcessStatus API used solely for the peak-memory readout) — no
# extra crate, unlike `mock`/`tracing`/`record` — and metrics are a specialized
# add-on most callers don't need.
= ["windows-sys/Win32_System_ProcessStatus"]
# Whole-tree resource caps: `ResourceLimits`, the `max_memory`/`max_processes`/
# `cpu_quota` builders on `ProcessGroupOptions`, and `Error::ResourceLimit`.
# Implies `stats` as policy — resource read and write travel together toward the
# possible `processkit-resource` split (see ideas/) — not as a code dependency.
= ["stats"]
# Tree control beyond contain+kill: `Signal` and
# `ProcessGroup::{signal, suspend, resume, members, adopt}`. Gated because it
# matches the layer a future `processkit-sys` split would carve out — assessed at
# the 3.0-planning prompt and deferred (no concrete consumer; a layered split is
# additive/non-breaking, so it needs no major and stays consumer-gated, not
# window-gated): decisions/sync-core-processkit-sys-split.md.
= []
# Expose the `mockall`-generated `MockRunner` for consumers' tests.
= ["dep:mockall"]
# Emit a `tracing` event per command run (program, exit code, duration —
# argv/env are deliberately not logged: they routinely carry secrets).
= ["dep:tracing"]
# Emit `metrics` counters/histograms over data the crate already computes: run and
# spawn counters, run/teardown duration histograms, an exit-code/timeout/cancel/
# signal tally, and retry/supervisor-restart/storm events. Labels carry only
# program name / mechanism / outcome class / exit code — never argv or env values
# (same secret-hygiene rule as `tracing`), and never an unbounded-cardinality value
# like a pid or a full argv. Off by default; additive — with the feature off the
# emission sites compile out entirely (a pure visibility gate).
= ["dep:metrics"]
# Record/replay cassettes over the `ProcessRunner` seam: `RecordReplayRunner`
# records real `Invocation → ProcessResult` pairs to a JSON fixture and replays
# them hermetically. Off by default; pulls in serde + serde_json.
= ["dep:serde", "dep:serde_json"]
# Opt-in single-master-fd PTY launch mode (`Command::use_pty`): spawn the child
# under a pseudo-terminal — `openpty` (Unix) / `CreatePseudoConsole` ConPTY
# (Windows) — instead of three independent pipes, so tools that demand a
# controlling terminal (an `isatty()`-gated agentic CLI, a `sudo`/`ssh` password
# prompt) work. stdout and stderr are MERGED onto the one master, so the
# `on_stderr_line`/stderr split collapses in this mode (see `Command::use_pty`).
# Containment is unchanged — the PTY child still lives in the same Job Object /
# cgroup / process group, so kill-on-drop reaps the whole tree. Off by default
# and additive: with the feature off (or on but `use_pty` unset) the existing
# three-pipe behavior is byte-identical. On Windows it needs one extra
# `windows-sys` FFI feature (`CreatePipe` for the ConPTY I/O pipes — the ConPTY,
# `STARTUPINFOEXW` proc-thread-attribute, and raw `CreateProcessW` APIs are
# already covered by the `Win32_System_Console`/`Win32_System_Threading` features
# enabled below); on Unix it needs no new crate (`openpty`/termios are `libc`,
# already a unix dep).
= ["windows-sys/Win32_System_Pipes"]
[]
# Win32 Job Object FFI for kill-on-close process trees: CreateJobObjectW /
# SetInformationJobObject / AssignProcessToJobObject / TerminateJobObject plus
# the HANDLE type & CloseHandle. Kept on the same major as tokio/mio's copy so
# the lockfile carries a single windows-sys.
= { = "0.61", = [
"Win32_Foundation",
"Win32_System_JobObjects",
# JOBOBJECT_EXTENDED_LIMIT_INFORMATION + GetProcessTimes (per-process CPU);
# also CREATE_SUSPENDED / OpenThread / ResumeThread for race-free containment
# (spawn suspended → assign to job → resume the primary thread).
"Win32_System_Threading",
# CreateJobObjectW's signature references SECURITY_ATTRIBUTES.
"Win32_Security",
# Win32_System_ProcessStatus (K32GetProcessMemoryInfo, peak working set) is
# enabled by the `stats` feature above — it serves only the metrics readout.
# Thread snapshot (CreateToolhelp32Snapshot / Thread32First/Next) to find a
# suspended child's primary thread for ResumeThread — std/tokio expose only
# the process handle, not the PROCESS_INFORMATION thread handle.
"Win32_System_Diagnostics_ToolHelp",
# GenerateConsoleCtrlEvent + CTRL_BREAK_EVENT for the opt-in graceful
# teardown: a direct child spawned CREATE_NEW_PROCESS_GROUP is sent a console
# CTRL_BREAK before the grace window, so a console child can shut down softly
# (Command::windows_graceful_ctrl_break). See src/sys/windows.rs.
"Win32_System_Console",
# EnumWindows + GetWindowThreadProcessId + PostMessageW + WM_CLOSE for the
# best-effort GUI-graceful tier: before the hard TerminateJobObject, every
# top-level window owned by a live job member is sent WM_CLOSE (posted, never
# sent, so a hung window can't block us) so a windowed child (Electron app,
# desktop tool, windowed service) can close cleanly within the grace. Same
# precedent as Win32_System_Console above. See src/sys/windows.rs.
"Win32_UI_WindowsAndMessaging",
] }
[]
# Raw syscalls std can't express: the POSIX process-group backend (setpgid /
# killpg / kill for teardown and liveness probes) on every unix, plus — on Linux
# — joining cgroup.procs in pre_exec (async-signal-safe getpid/open/write/close).
= "0.2.149"
[]
# `#[tokio::test]` plus a multi-threaded runtime to drive the async tests;
# `test-util` provides the paused clock for the supervisor's backoff-timing
# tests (virtual time instead of real sleeps); `net` binds the ephemeral
# TcpListener the `wait_for_port` integration test probes against.
# Kept in sync with the `[dependencies]` tokio floor above (`1.39`, forced by
# the `abort_handle`/`as_std_mut`/`mio`-`ntapi` build-honesty fixes — see that
# entry for why).
= { = "1.39", = ["macros", "rt-multi-thread", "time", "io-util", "test-util", "net"] }
# Auto-cleaned, parallel-safe temp files for the `record` cassette round-trip
# tests (hand-rolled temp_dir paths leak and race under parallel test runs).
= "3"
# Property-based tests over the line pump + `encoding_rs` decoder
# (`src/pump.rs`): generates arbitrary byte streams chunked at arbitrary read
# boundaries to check line/byte counters and multibyte reassembly hold for
# any chunking, and that no chunking makes the pump panic.
= "1"
# Benchmark harness for `benches/pump.rs` — baselines the output pump's hot
# paths (line splitting, decoding, tee/handler) ahead of optimizing them.
# `async_tokio` runs an async benchmark body on our own tokio runtime instead
# of criterion's default synchronous bencher; `harness = false` on the
# `[[bench]]` entry below hands `main` to criterion instead of `libtest`.
= { = "0.8", = ["async_tokio"] }
[[]]
= "pump"
= false
[[]]
= "compare"
= false
[]
# Liveness probe (OpenProcess) in the integration tests that prove a grandchild is
# reaped with its job — integration tests can't see the non-dev windows-sys.
# `Win32_System_Console` (AllocConsole) lets the CTRL_BREAK graceful test attach
# a console when the harness was launched without one, so the console event the
# mechanism relies on has somewhere to route.
= { = "0.61", = [
"Win32_Foundation",
"Win32_System_Threading",
"Win32_System_Console",
# The nested-jobs integration test (groups.rs `mod nested_job`) drives Job
# Objects from the test itself, mirroring src/sys/windows.rs at the user level:
# CreateJobObjectW + SetInformationJobObject (JOBOBJECT_EXTENDED_LIMIT_INFORMATION
# with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) build the *outer* job,
# AssignProcessToJobObject nests the helper process into it, and IsProcessInJob
# is the helper's "am I actually inside a job?" sanity check.
"Win32_System_JobObjects",
# CreateJobObjectW's signature references SECURITY_ATTRIBUTES — same reason the
# non-dev windows-sys enables this above.
"Win32_Security",
] }
[]
# Liveness probe (kill(pid, 0)) and the root gate (geteuid) in the setsid /
# privilege-drop integration tests — the test crate can't see the non-dev libc.
= "0.2.149"