Exit codes for main

When a CLI finishes, the OS only sees an 8-bit exit status. id_effect distinguishes richer outcomes (Exit, Cause); at the process edge you collapse that into std::process::ExitCode.

Result from run_blocking

run_blocking returns Result<A, E>:

OutcomeSuggested CLI byteNotes
Ok(_)0success
Err(_)1typed / expected failure (id_effect_cli::exit_code_for_result)

id_effect_cli::run_main uses this mapping and prints Err with Debug to stderr.

Full Exit / Cause

When you have an Exit (for example from tests or a custom driver), map leaf causes as follows — composite Cause::Both / Cause::Then use the maximum byte so “stronger” failures dominate:

PatternByteMeaning
Exit::Success0OK
Cause::Fail(_)1expected typed failure
Cause::Die(_)101defect (panic-style message)
Cause::Interrupt(_)130cancellation (same convention many shells use for SIGINT)
Cause::Both / Cause::Thenmax(left, right)recurse

Helpers: exit_code_for_exit, exit_code_for_cause, cause_max_exit_byte.

Practical guidance

  • Use 1 for “the command could not complete successfully” (missing flag, validation error, upstream HTTP 4xx mapped to your E, …).
  • Reserve 101 for “the program detected an internal defect” — corresponds to Cause::Die in structured runs (see cause_max_exit_byte).
  • Use 130 only when you surface fiber interruption to the process edge (rare in simple CLIs).

If you need richer machine output, prefer stderr JSON or a dedicated output file — do not overload exit codes beyond what your operators can act on.