# Standard Exit Code Conventions
## Rule
Every CLI tool MUST exit with meaningful exit codes. Exit 0 on success, 1 on general failure, 2 on usage errors. Never exit 0 when an error occurred.
## Standard Exit Codes
| Code | Meaning | When to Use |
|------|---------|-------------|
| 0 | Success | Command completed successfully |
| 1 | General error | Runtime failures, exceptions |
| 2 | Usage error | Invalid arguments, missing flags |
| 126 | Cannot execute | Permission denied |
| 127 | Command not found | Dependency missing |
| 128+N | Signal N | Killed by signal (e.g., 130 = Ctrl+C) |
## Good Examples
```bash
#!/usr/bin/env bash
set -euo pipefail
main() {
if [[ $# -lt 1 ]]; then
echo "Usage: $(basename "$0") <filename>" >&2
exit 2 # Usage error
fi
local file="${1}"
if [[ ! -f "${file}" ]]; then
echo "Error: File not found: ${file}" >&2
exit 1 # General error
fi
process_file "${file}"
exit 0 # Explicit success
}
main "$@"
```
## Bad Examples
```bash
# BAD: Exit 0 on error — breaks pipelines
if ! process_file "${file}"; then
echo "Error processing file"
exit 0 # Callers think this succeeded!
fi
# BAD: No exit code — relies on last command
echo "Done"
# If prior command failed silently, exit code is unpredictable
# BAD: Using random exit codes
exit 42 # What does 42 mean? Undocumented
```
## Using Exit Codes in Pipelines
```bash
# Callers depend on correct exit codes
if my-tool --validate config.yml; then
echo "Config is valid"
else
echo "Config validation failed (exit: $?)"
exit 1
fi
# Conditional chaining
my-tool build && my-tool deploy || echo "Pipeline failed"
```
## Enforcement
- Test exit codes in CI: `my-tool --bad-flag; test $? -eq 2`
- Document exit codes in --help output
- Never silence errors with `|| true` in production scripts