Command Type
An immutable description of a process to run. Build it fluently — each method returns a new `Command`. The value is the *cold* description of a run; the process is launched only when a verb (`Runner.run`, `Command.start`, …) is invoked. Use the instance methods (`cmd.Arg "x"`) or the `Command` module's pipe-friendly functions (`cmd |> Command.arg "x"`).
Constructors
| Constructor |
Description
|
|
Start a new command for the given program (resolved on PATH unless a path is given). `program` must be non-empty and must not contain an embedded NUL (`'\000'`) — either would let the actual spawned command diverge from the one requested (see `CommandConfig.rejectEmbeddedNul`).
|
Instance members
| Instance member |
Description
|
Append a single argument. `value` must not contain an embedded NUL (`'\000'`) — see `CommandConfig.rejectEmbeddedNul`.
|
|
Append several arguments, in order. Every element must be non-null (a null element inside an otherwise non-null `seq` — a C#-reachable shape `ArgumentNullException.ThrowIfNull values` on the sequence itself cannot catch) and must not contain an embedded NUL (`'\000'`); the exception names the offending element by index (`Args[2]`).
|
|
|
The arguments, in order.
|
|
Also cancel the run when `cancellationToken` fires (in addition to any verb token). This binds the token to the **completion** verbs — `RunAsync`/`Output*`/`ExitCodeAsync`/`ProbeAsync`/`ParseAsync`/`FirstLineAsync`: they drive the child to completion, so they watch the token for the whole run and turn a fired token into `ProcessError.Cancelled`. It does **not** reach a live `StartAsync`/`SpawnAsync` handle. On that path the verb's own token is checked exactly once, before the actual spawn (an already-cancelled token short-circuits to `ProcessError.Cancelled` and starts nothing); once the child is running, neither this `CancelOn` token nor the token passed to `StartAsync` is tracked. A live handle is caller-driven — cancel or reap it yourself: dispose it, call its `Kill`, or register your own callback on the token that calls `Kill`.
|
|
The configured run timeout, if any.
|
|
Windows: run the child with `CREATE_NO_WINDOW`, so a console child spawned from a GUI app does not flash a console window. No effect on Unix.
|
|
Set the working directory for the run. `directory` must not contain an embedded NUL (`'\000'`) — see `CommandConfig.rejectEmbeddedNul`.
|
|
|
Set an environment variable for the child. `key` must be non-empty, must not contain `=`, and neither `key` nor `value` may contain an embedded NUL (`'\000'`) — all rejected with `ArgumentException` (either would corrupt the child's environment block, or let it diverge from what was requested).
|
|
|
Start the child's environment empty instead of inheriting the parent's.
|
|
Remove an inherited environment variable from the child. `key` must be non-empty and must not contain `=` (same rule as `Env`).
|
Run the child under this Unix group id (`setgid`) — see `Uid` for the mechanism, platform notes, and privilege requirement. `setgid` is applied before any `setuid`, so the two compose into a correct privilege drop. `gid` must be non-negative.
|
|
|
Set the child's Unix **supplementary groups**, *replacing* the inherited set — the missing third leg of a correct privilege drop, next to `Uid`/`Gid`. A bare `Uid`/`Gid`/`User` drop *clears* the parent's supplementary groups (`setpriv --clear-groups`) so the child never keeps root's; pass the target user's groups here to grant them back (e.g. a service user's `docker`/`video`/`adm` membership), or `[]` to keep the cleared default explicitly. The gids are applied verbatim — they need not name existing `/etc/group` entries. Because it rides the same `setpriv` helper as the uid/gid drop (mapped to `setpriv --groups`), it is meaningful only **alongside a `Uid` or `Gid` drop**: `Groups` set without either is refused at spawn with `ProcessError.Spawn` rather than silently ignored (never a silent no-op). **Unix-only:** on Windows (no equivalent) a set value fails the spawn with `ProcessError.Unsupported`, exactly like `Uid`/`Gid`. Every gid must be non-negative — rejected with `ArgumentOutOfRangeException` at the builder boundary, naming the offending element by index (`Groups[2]`).
|
|
Kill the run when it produces **no output** — on neither stdout nor stderr — for `duration`, reporting the result as `Outcome.TimedOut`. Every chunk of output resets the deadline, so a run that keeps streaming stays alive; one that hangs after going quiet is killed. This is distinct from `Timeout`, which bounds the *total* run length regardless of output: the two are independent and may both be set, each firing on its own condition. Idle activity is measured at byte granularity across every verb (buffered capture, streaming, raw bytes, and the drained `WaitAsync`/`ProfileAsync`), so even a run whose output is discarded — or a single long newline-free blob — counts as active. A negative `duration` is rejected (`ArgumentOutOfRangeException`, matching `Timeout`); one larger than ~24.8 days is treated as no idle deadline. Honours `TimeoutGrace` (a graceful stop, then a hard kill) exactly as `Timeout`.
|
|
Hand the child the parent process's **own standard input** directly — inherited, with no pipe and no feeder — for interactive/console programs that read from the terminal (an editor launched by `git commit`, a tool that prompts the user, a pipe from the parent's own stdin). This is the stdin analogue of `StdioMode.Inherit` for stdout/stderr. Because there is no stdin pipe, it is incompatible with the pipe-based stdin knobs and rejected together with them at the builder boundary (`ArgumentException`, in either chaining order): a feeder source (`Stdin`) and `KeepStdinOpen`. For the same reason `RunningProcess.TakeStdin` yields `None` for an inherited-stdin child (there is no interactive pipe to hand out). The capture/streaming verbs are unaffected — only the child's stdin wiring changes. Repeatable: a retry or a supervisor restart re-inherits the parent's stdin, so `InheritStdin` is never refused by the one-shot-source retry guard.
|
|
Keep the child's stdin pipe open after the source is exhausted (for interactive writing via `RunningProcess.TakeStdin`). Rejected (`ArgumentException`) when `InheritStdin` is already set — an inherited stdin has no pipe to keep open.
|
|
Frame **both** captured/streamed streams' lines with `terminator`. See `StdoutLineTerminator` for what the line framing governs (and what it leaves byte-exact).
|
|
|
|
Merge the child's standard **error** into its standard **output** at the OS level — the library equivalent of a shell `2>&1`. The native spawn points the child's stderr at the very same pipe/handle as its stdout (POSIX `dup2` of fd 2 onto stdout's target; Windows shares one handle across `STARTUPINFO.hStdOutput`/`hStdError`), so the two streams interleave **honestly, byte for byte** on the single stdout stream — the real terminal-order `2>&1` view that the post-hoc `ProcessResult.Combined` (a concatenation of two *separately* captured streams) cannot reproduce. It works uniformly for the buffering verbs, the streaming verbs (`StdoutLinesAsync`/ `OutputEventsAsync`), and pipeline stages. The default is off (separate stdout/stderr, unchanged). **There is then no separate stderr stream, and the API reflects that honestly** (never a silent downgrade): `ProcessResult.Stderr` is always empty, the streamed stderr stream is absent, and `OutputEventsAsync` emits only `OutputEvent.Stdout` events — the stderr lines already live, in order, in the stdout byte stream. Because the merge removes the separate stream, the separate-stderr **observation** knobs are rejected at the builder boundary with an `ArgumentException` (in either chaining order) rather than silently never firing: `StderrTee` and `OnStderrLine` cannot be combined with `MergeStderr`. The remaining stderr knobs are documented **no-ops** under merge — the merged bytes follow stdout's settings: `StderrEncoding` (the merged stream decodes with `StdoutEncoding`), `StderrLineTerminator` (framed with `StdoutLineTerminator`), and the `Stderr` `StdioMode` (stderr follows stdout's destination). These are not rejected because `Encoding()` and `LineTerminator()` set the stdout+stderr pair together, so rejecting them would make those pair setters conflict with `MergeStderr`. Inside a `Pipeline`, `MergeStderr` is allowed only on the **last** stage — its stdout is the pipeline's captured output, so a `2>&1` there captures the final stage's merged output. Setting it on any earlier stage is rejected (`ArgumentException`) the moment the stage stops being last: a pipeline wires each stage's stdout into the next stage's stdin, so merging an intermediate stage's stderr would inject it into the downstream stage's input data.
|
|
Replace the set of exit codes treated as success (the default is `{0}`) — this is what `ProcessResult.IsSuccess`, `ensureSuccess`, and the `RunAsync` verbs check. The codes *replace* the default rather than adding to it, so pass `[0; 3]` to accept both `0` and `3`. An empty set is a **no-op** — the previously configured codes are kept (it never resets the accepted set), so it can't accidentally clear a caller's `[0; 3]` down to nothing.
|
|
|
|
|
|
|
|
Launch the child — and the process tree it spawns — at a lower (or higher) CPU-scheduling `priority`: a Windows priority class set at process creation, or a Unix `nice` value applied via `setpriority`. Supported on both platforms (never `ProcessError.Unsupported`); the default (unset) leaves the OS default. Raising priority above the inherited level on Unix (`Priority.High`/`Priority.AboveNormal`) needs privilege — without it the spawn fails with `ProcessError.Spawn` rather than silently running lower. See `Priority`.
|
Full Usage:
this.Program
Returns: string
|
The program to run.
|
Full Usage:
this.Retry
Parameters:
int
delay : TimeSpan
shouldRetry : Func<ProcessError, bool>
Returns: Command
|
Run the command up to `maxAttempts` times **in total** (the initial run plus up to `maxAttempts - 1` retries), waiting `delay` between attempts, while `shouldRetry` returns true for the error. `maxAttempts` of `0` or `1` both mean a single run — a command always runs at least once.
|
|
Explicitly disable retrying for this command, overriding any `Retry` policy already on it — including one inherited from a `CliClient.WithDefaults` template. Distinct from never having called `Retry` at all: an unset `Retry` still accepts a client's default, `RetryNever` refuses it. The command always runs exactly once. A later `Retry(...)` call in the same chain re-opts back in (the last call wins).
|
|
Detach the child into a **new session** (`setsid()`): its own session and process group, with no controlling terminal. **Unix-only:** on Windows a requested detach fails the spawn with `ProcessError.Unsupported`. `setsid()` makes the child a new process-group leader (pgid == pid), so the kill-on-drop group teardown (`killpg`) still reaches the whole session — containment is preserved; the new session simply replaces the group's default `POSIX_SPAWN_SETPGROUP` for this command. A `setsid()` the OS refuses fails the spawn with `ProcessError.Spawn`.
|
|
|
|
|
|
Frame captured/streamed **stderr** lines with `terminator` (default `LineTerminator.Lf`). See `StdoutLineTerminator`; the stdout framing is left untouched.
|
|
|
|
|
|
|
|
|
|
Frame captured/streamed **stdout** lines with `terminator` (default `LineTerminator.Lf` — split on `\n`). Pass `LineTerminator.Cr`/`Any` to split carriage-return progress output on a bare `\r`. Affects only the line-pumped path (streaming, per-line handlers, `OutputStringAsync`); the raw `OutputBytesAsync` bytes and the tees stay byte-exact.
|
|
|
|
Opt in to a bounded/backpressure channel for the streaming verbs (`StdoutLinesAsync`/ `OutputEventsAsync`/`WaitForLineAsync`). Unset (the default) keeps the unbounded streaming channel ProcessKit has always used. See [Streaming](../../docs/streaming.md) for the backpressure deadlock footgun before opting in to `StreamFullMode.Backpressure`.
|
|
|
|
|
Run the child under this Unix user id (`setuid`). **Unix-only:** on Windows (which has no equivalent) a requested uid fails the spawn with `ProcessError.Unsupported` rather than being silently ignored. Because `posix_spawn` has no uid attribute, a command with a uid (or `Gid`) is spawned through the `setpriv` helper (util-linux), which drops the gid/uid and clears the supplementary groups before `exec`ing the real program in place. Dropping to another user is **root-only** (`euid == 0`): a non-root caller asking for a different uid fails the spawn with `ProcessError.Spawn` (never a child that kept the parent's uid) — including one holding `CAP_SETUID`/`CAP_SETGID`, which the up-front check conservatively refuses rather than probes — as does a host with no `setpriv` (mainstream Linux has it; macOS/BSD do not). `uid` must be non-negative (rejected with `ArgumentOutOfRangeException` at the builder boundary). Pair with `Gid` (or `User`) for a full drop.
|
|
|
Set the child's Unix file-mode creation mask (`umask(2)`), controlling the default permissions of files it creates — pass the value you would give the `umask` shell builtin (e.g. `0o022`). Only the low permission bits are meaningful, as with the syscall itself. **Unix-only:** on Windows (which has no equivalent) a set mask fails the spawn with `ProcessError.Unsupported` rather than being silently ignored. The default (unset) leaves the inherited umask untouched. `mask` must be within `0..0o7777` (the meaningful permission-bit range); outside it an `ArgumentOutOfRangeException` is thrown at the builder boundary rather than being handed to `umask(2)` as-is.
|
|
Inside a pipeline, do not let this stage's non-zero exit fail the pipeline (it is still reported in the stage outcomes). Outside a pipeline this flag has no effect.
|
Run the child under this Unix user **and** group id — the common privilege-drop pair, equivalent to `.Gid(gid).Uid(uid)`. See `Uid` for the mechanism, ordering (`setgid` before `setuid`), supplementary-group clearing, platform notes, and privilege requirement. Both ids must be non-negative.
|
|
|
Windows: spawn the child as its own console process group (`CREATE_NEW_PROCESS_GROUP`), so that `ProcessGroup.Signal(Signal.Int)` / `Signal.Term` can deliver it a best-effort console **CTRL+BREAK** — the closest Windows analogue to a graceful `SIGINT`/`SIGTERM` — instead of the hard atomic Job-Object kill, giving a console child a chance to clean up. **Best-effort and console-only:** the event reaches only a console child that shares the caller's console. A child given its own or hidden console (via `CreateNoWindow`), or a parent that has no console at all, cannot receive it — the send then fails honestly with `ProcessError.Unsupported` rather than a silent downgrade — and even on a successful send delivery is not guaranteed (the child may install its own console handler). `Signal.Kill` is unaffected (always the atomic Job kill), and this has no effect on Unix, where signals reach the child's process group regardless. Note that `CREATE_NEW_PROCESS_GROUP` also disables the child's default CTRL+C handling, which is why the soft signal is delivered as CTRL+BREAK rather than CTRL+C.
|
Full Usage:
this.WorkingDirectory
Returns: string option
|
The working directory, when overridden.
|
ProcessKit API Reference