Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process

When a process cannot complete as requested, Process.toFlow fails with ProcessError. This is the Flow's normal typed error value, not an exception thrown by the child program. Run the Flow and match Exit.Failure(Cause.Fail error) when this boundary needs to decide what to report or return.

The failure cases

ProcessError case When it occurs Details available
StartFailed The executable cannot be started: it is missing, not executable, or the operating system rejects startup. A pipeline can also fail this way after earlier stages have started. ProcessStartFailure.Command and Message
TimedOut The specification's configured timeout elapses before every stage completes. Redacted specification and timeout duration
Canceled The caller cancels the enclosing Flow or ends stream consumption early. Cancellation message
StageFailed A started command exits with a code outside its configured success-code set. This is the usual result for a command that reports failure with a nonzero exit code. Failed StageResult and the complete ProcessResult
IoFailed Axial cannot read, write, or route one of the process streams or configured file targets. I/O error message

For every timeout, cancellation, and partial pipeline startup, Axial terminates stages that did start before the Flow finishes. The typed error tells the caller what happened after cleanup, rather than leaving a child process running in the background.

Match the error when the caller needs a policy

match exit with
| Exit.Failure(Cause.Fail(ProcessError.StartFailed failure)) ->
    eprintfn "could not start %s: %s" failure.Command failure.Message
| Exit.Failure(Cause.Fail(ProcessError.TimedOut failure)) ->
    eprintfn "%s exceeded %O" failure.Specification failure.Timeout
| Exit.Failure(Cause.Fail(ProcessError.StageFailed failure)) ->
    eprintfn "stage %d exited %d" failure.Stage.Stage failure.Stage.ExitCode
| _ -> ()

ProcessError.describe formats a redacted diagnostic. ProcessError.exitCode maps a failed stage to its native exit code, timeout to 124, cancellation to 130, and startup or I/O failure to 1. The host run applies the same mapping automatically.

What a successful result contains

A ProcessResult is returned only when every stage satisfies its success policy. It contains the final stdout and stderr text, exact captured bytes, every stage's exit code, start times, durations, and bounded stderr tails. Change Process.successCodes when a command treats other exit codes as successful.

let! result = specification |> Process.toFlow

for stage in result.Stages do
    printfn "[%d] %s => %d (%O)" stage.Stage stage.Command stage.ExitCode stage.Duration