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>:
| Outcome | Suggested CLI byte | Notes |
|---|---|---|
Ok(_) | 0 | success |
Err(_) | 1 | typed / 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:
| Pattern | Byte | Meaning |
|---|---|---|
Exit::Success | 0 | OK |
Cause::Fail(_) | 1 | expected typed failure |
Cause::Die(_) | 101 | defect (panic-style message) |
Cause::Interrupt(_) | 130 | cancellation (same convention many shells use for SIGINT) |
Cause::Both / Cause::Then | max(left, right) | recurse |
Helpers: exit_code_for_exit, exit_code_for_cause, cause_max_exit_byte.
Practical guidance
- Use
1for “the command could not complete successfully” (missing flag, validation error, upstream HTTP 4xx mapped to yourE, …). - Reserve
101for “the program detected an internal defect” — corresponds toCause::Diein structured runs (seecause_max_exit_byte). - Use
130only 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.