Pipeline Type
An immutable left-to-right chain of commands wired stdout -> stdin, with **no shell** involved: each stage's standard output feeds the next stage's standard input directly. The whole chain runs inside one shared kill-on-dispose group, so cancelling, timing out, or disposing the run reaps every stage together. Build it by piping commands (`a.Pipe(b).Pipe(c)`), then run it to completion with the same run-and-capture verbs a single command exposes (`RunAsync`/`OutputStringAsync`/`OutputBytesAsync`/`ExitCodeAsync`/ `ProbeAsync`/`ParseAsync`/`TryParseAsync`). To stream the final stage's stdout as it arrives instead of buffering the whole chain, start a live session with `StartAsync` (→ `PipelineSession`), the pipeline analogue of `Command.StartAsync` → `RunningProcess`. The exit status follows shell **pipefail**: the rightmost stage that did not exit with an accepted code (its `Command.OkCodes`, `{0}` by default) determines the result, unless that stage opted out with `Command.UncheckedInPipe`. When no checked stage failed, the final stage's real outcome and diagnostics are returned; an unchecked voluntary exit remains successful because its actual code is included in the result's accepted codes, not because the outcome is rewritten. Per-stage I/O config that applies inside a pipeline: each stage's `OkCodes` (pipefail) and `UncheckedInPipe`, the last stage's `StdoutEncoding`, `StdoutTee`, and `OutputBuffer` **byte** cap (`MaxBytes` + `Overflow`, applied to the captured stdout — its `MaxLines` never applies to a raw byte capture), stage 0's `Stdin` source (feeding the whole chain), and the chain-level `Timeout` / `CancelOn`. Every stage's stderr is likewise drained under its OWN `OutputBuffer`'s **byte** cap (`MaxBytes` + `Overflow`; a stage without `MaxBytes` set keeps its stderr unbounded, as before), so a chatty stage can never exhaust memory regardless of its position in the chain. Per-stage *stdout/ stderr observation* hooks are still **not** applied — intermediate stages' `StdoutTee`, every stage's `StderrTee`, and `OnStdoutLine`/`OnStderrLine` — because the chain wires stdout into the next stage's stdin and captures only the final stage's output. Observe an individual command by running it on its own, not as a pipeline stage. A stage's `CapturePolicy` is bound by that same boundary — every capture a pipeline makes (the final stdout, and each stage's stderr) is a RAW BYTE capture with no line framing, which is also what leaves `OutputBuffer.MaxLines` inapplicable here — but it is **rejected** rather than left unapplied, because a redaction hook that quietly stops running hands back the secret it was installed to remove (see the rejected-config paragraph below). Per-stage config a pipeline cannot honour is **rejected when the stage is piped** (an `ArgumentException` from `Pipe`, naming the field and stage index), rather than silently dropped: a `Stdin` source on any stage *after the first* (its stdin is always rewired to the previous stage's stdout — only stage 0 may set a source), a per-stage `Timeout` on any stage (only the chain-level `Pipeline.Timeout` bounds a pipeline; `Command.Timeout` on a stage never fires), a per-stage `IdleTimeout` on any stage (a pipeline captures only the last stage's output and does not monitor per-stage output activity, so a stage's own idle deadline can never fire), a per-stage `Retry` on any stage (retry is a verb-layer mechanism, and stages spawn directly, bypassing it), and a per-stage `CancelOn` on any stage (a stage's own `Command.CancelOn` token is likewise a verb-layer mechanism the direct stage spawn bypasses; only the chain-level `Pipeline.CancelOn` cancels a pipeline), `KeepStdinOpen` on any stage (the pipeline exposes no per-stage `RunningProcess.TakeStdin` handle through which the kept-open pipe could be used), a per-stage `CapturePolicy` on any stage (every capture a pipeline makes is a raw byte capture, so the seam has no decoded line to shape — run such a command on its own to scrub what it captured), `StdoutToFile` on any stage (stdout is instead pipeline wiring or final captured output), and `Stdout(StdioMode.Null|Inherit)` on any stage (the pipeline must use a pipe rather than silently override that destination). `StderrToFile` remains supported: it directs that stage's diagnostics to the requested file while the chain continues to carry stdout. Set the deadline on the pipeline, cancel the whole chain with `Pipeline.CancelOn`, feed stage 0, or run the command on its own. `MergeStderr` (a shell `2>&1`) is allowed only on the **last** stage — its stdout is the pipeline's captured output, so merging captures the final stage's combined stdout+stderr. On any earlier stage it is rejected (`ArgumentException`) the moment the stage stops being last (another stage is appended after it): a pipeline wires each stage's stdout into the next stage's stdin, so an OS-level merge on an intermediate stage would inject its stderr into the downstream stage's input data. Observability is whole-pipeline, not per-stage: running the chain emits one `Log.spawn`/`Log.exit` pair (plus `Log.timeout` on a timeout) and one `Diag.runStarted`/`runCompleted`/`runEnded` triple, all sharing a single run id — never one set per stage. Stage 0's `Logger` becomes the pipeline's logger (a per-stage `Logger` on any *other* stage has no effect — set it on stage 0, or observe an individual command by running it on its own); the `program` tag/label is a composite of every stage's name, joined `"a | b | c"` (built only from `Command.Program`, never argv/env, so the argv/env-never-logged invariant holds for a multi-stage run too). Stage 0 likewise owns `StopSignal`, because graceful shutdown broadcasts one soft signal to the whole chain — and, for the same reason, `CancelGrace`/`CancelSignal`: a cancelled chain (`Pipeline.CancelOn` or the verb's own token) is torn down through ONE soft signal and ONE grace window over the shared group, so setting either on stage 0 makes the whole chain's cancellation graceful, and setting it on a later stage is rejected rather than ignored. The chain-level `Pipeline.Timeout` is untouched by that knob and keeps its immediate hard kill. `StreamBuffer` depends on how the pipeline is run. For buffered verbs (`RunAsync`, `OutputStringAsync`, `OutputBytesAsync`, `ExitCodeAsync`, and similar verbs used without `StartAsync`), it is inapplicable because there is no streaming consumer to receive the policy. `StartAsync`, however, returns a live `PipelineSession`: the last stage's `StreamBuffer` policy is applied to the session's stdout channel, including its bounded-buffer/backpressure behavior. `KeepStdinOpen` is rejected with `ArgumentException`, not accepted as a silent no-op. Although `PipelineSession` is a live handle, it does not expose interactive stdin: the pipeline wires each stage after the first from the previous stage's stdout itself, and exposes no user-reachable `RunningProcess.TakeStdin` handle.
Instance members
| Instance member |
Description
|
|
Also cancel the whole pipeline when `cancellationToken` fires (in addition to any verb token).
|
Full Usage:
this.ExitCodeAsync
Parameters:
CancellationToken
Returns: Task<Result<int, ProcessError>>
|
The pipefail exit code. A signal kill or timeout errors instead of inventing a sentinel.
|
Full Usage:
this.OutputBytesAsync
Parameters:
CancellationToken
Returns: Task<Result<ProcessResult<byte[]>, ProcessError>>
|
Run the pipeline to completion, capturing the last stage's stdout as raw bytes. A non-zero pipefail exit is data here, not an error.
|
Full Usage:
this.OutputJsonAsync
Parameters:
JsonTypeInfo<'T>
?cancellationToken : CancellationToken
Returns: Task<Result<'T, ProcessError>>
Type parameters: 'T |
Require a successful pipefail exit and deserialize the trimmed stdout using source-generated `JsonTypeInfo<'T>` metadata. Invalid JSON becomes `ProcessError.Parse`; unlike the `JsonSerializerOptions` overload, this overload is safe for trimmed and NativeAOT applications.
|
Full Usage:
this.OutputJsonAsync
Parameters:
JsonSerializerOptions
?cancellationToken : CancellationToken
Returns: Task<Result<'T, ProcessError>>
Type parameters: 'T |
Require a successful pipefail exit and deserialize the trimmed stdout as JSON into a `'T` via `System.Text.Json` (`options` omitted uses the BCL defaults); invalid JSON becomes `ProcessError.Parse`, just like `ParseAsync`. Give an explicit type argument — there is no parser argument to infer `'T` from. **Trimming / AOT:** deserializes via reflection-based `System.Text.Json` (`JsonSerializer.Deserialize(string, Type, JsonSerializerOptions)`), so it is not trim-/AOT-safe — pass `options` with a source-generated `JsonSerializerContext`/`JsonTypeInfo<'T>` resolver, or avoid this verb, in a trimmed/NativeAOT app.
|
Full Usage:
this.OutputStringAsync
Parameters:
CancellationToken
Returns: Task<Result<ProcessResult<string>, ProcessError>>
|
Run the pipeline to completion, capturing the last stage's stdout as decoded text (using the last stage's stdout encoding). A non-zero pipefail exit is data here, not an error.
|
Full Usage:
this.ParseAsync
Parameters:
Func<string, 'T>
?cancellationToken : CancellationToken
Returns: Task<Result<'T, ProcessError>>
Type parameters: 'T |
Require a successful pipefail exit and parse the trimmed stdout into a `'T`; a thrown parser error becomes `ProcessError.Parse`.
|
Append another stage; its stdin is fed from the current last stage's stdout. Rejects (`ArgumentException`) a stage that sets a per-stage `Timeout`/`IdleTimeout`/`Retry`/`CancelOn`/`CapturePolicy`, `KeepStdinOpen`, `StdoutToFile`, non-piped `Stdout`, or a `Stdin` source — a pipeline cannot honour those (see the type doc); the appended stage is always after the first.
|
|
Full Usage:
this.ProbeAsync
Parameters:
CancellationToken
Returns: Task<Result<bool, ProcessError>>
|
Read the pipefail exit code as a yes/no answer: 0 -> true, 1 -> false, anything else errors.
|
Full Usage:
this.RunAsync
Parameters:
CancellationToken
Returns: Task<Result<string, ProcessError>>
|
Require a successful pipefail exit and return the last stage's stdout, trailing whitespace trimmed. Any checked stage that did not exit 0 fails the pipeline. Output a bounded `OutputBuffer` byte cap truncated (`DropOldest`/`DropNewest` on the captured last-stage stdout, or on the stderr this result publishes) is refused with `ProcessError.OutputTooLarge` instead of being returned as if whole — `OutputStringAsync` is the lenient path, handing back the bounded payload with `ProcessResult.Truncated` set. That refusal always names the **last** stage and quotes that same stage's byte ceiling — the only ceiling a verb applied to the chain's captured stdout can name. The two cannot come apart here: an earlier stage is the pipefail representative only when it is a CHECKED FAILURE (`PipelineClassify.representative`), and the success check above has already turned such a chain into its own `Exit`/`Signalled`/`Timeout` error before truncation is looked at; with no checked failure the real last stage stands, whether or not it opted out of pipefail with `UncheckedInPipe`. So what this refusal reports is the last stage's own capture — its stdout, or the stderr published with it — while a clipped stderr on any other stage stays local to diagnostics the result does not publish (`PipelineClassify.resultTruncated`).
|
Full Usage:
this.RunUnitAsync
Parameters:
CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like `RunAsync`, but discard the captured output — including when a bounded buffer truncated it: a side-effect run promises nothing about stdout, so a clean pipefail outcome stays `Ok`.
|
Full Usage:
this.StartAsync
Parameters:
CancellationToken
Returns: Task<Result<PipelineSession, ProcessError>>
|
Start the pipeline as a live **streaming session** instead of running it to completion: spawn the whole chain into one shared kill-on-dispose group and hand back a `PipelineSession` that streams the FINAL stage's stdout line by line (`StdoutLinesAsync`/`OutputEventsAsync`), waits for the whole chain with the same pipefail classification the buffering verbs use (`FinishAsync`), and stops/reaps the entire chain (`StopAsync`/dispose). This is the pipeline analogue of `Command.StartAsync` → `RunningProcess`, for long-running or interactive pipelines (`journalctl -f | grep …`) whose final output must be read as it appears rather than buffered until the whole chain exits. `cancellationToken` is checked once, before spawning (an already-cancelled token reports `ProcessError.Cancelled` and starts nothing). The chain-level `Timeout`/`CancelOn` set on this pipeline still apply to the live session: either one firing hard-kills the whole tree, and a subsequent `FinishAsync` then reports the run as `TimedOut`/`Cancelled`. Otherwise the session is caller-driven — stop it with `StopAsync`/`Kill`/dispose. A stage that fails to spawn tears down the partially started chain and returns its error, orphaning nothing.
|
|
|
Full Usage:
this.TryParseAsync
Parameters:
TryParser<'T>
?cancellationToken : CancellationToken
Returns: Task<Result<'T, ProcessError>>
Type parameters: 'T |
Like `ParseAsync`, but with the standard .NET try-parse shape: pass a BCL parser like `int.TryParse` with an explicit type argument (`TryParseAsync<int>(int.TryParse)` — needed because BCL `TryParse` is overloaded). A `false` return becomes `ProcessError.Parse`. (F# can use the `Result`-returning `Runner.tryParse`.)
|
ProcessKit API Reference