<!-- Generated by scripts/gen_exit_codes.py from sr/exit_codes.py — do not edit. -->

# sr — exit codes

Frozen contract, sr 7.0.0. Every code below is produced by exactly one
place in the code base (`sr/exit_codes.py`), and `tests/test_contracts.py`
fails if a value changes without the test being updated deliberately.

| Code | Constant | Meaning |
| --- | --- | --- |
| `0` | `OK` | success (for replace/find: at least one match was found) |
| `1` | `ERROR` | runtime error (I/O failure, corrupt session, internal bug) |
| `2` | `USAGE` | usage error (bad arguments, bad configuration file) |
| `3` | `NO_MATCH` | no match: the pattern was not found in any target file |
| `4` | `ROLLBACK_FAILED` | rollback failed for one or more files |
| `5` | `UPDATE_FAILED` | self-update failed (network, checksum, permissions) |
| `6` | `SELFTEST_FAILED` | selftest failed: installation is broken |
| `130` | `INTERRUPTED` | interrupted by the user (SIGINT) |

## Conventions

* **`0` vs `3`** follow the `grep`/`diff` convention: `0` means "there was
  something to do", `3` means "nothing matched". That lets a script write
  `sr find X || echo missing` without shell gymnastics, and makes
  `sr replace --dry-run` a usable predicate.
* **`1` vs `2`** separate "something went wrong while working" from "you asked
  for something impossible". A bad flag, an unreadable config file or a missing
  target directory is `2`; an I/O failure halfway through a run is `1`.
* **`130`** is the conventional `128 + SIGINT`, so shells report a normal
  Ctrl-C instead of a generic failure.
* Non-zero is never "partial success": if any file failed to write, the code is
  `1` even though other files were changed. The per-file detail is in the
  summary and in `--json` (`.totals.failed`, `.errors`).

## Reading the code in a script

```sh
sr replace "$OLD" "$NEW" -p '*.py' --yes -q
case $? in
  0) echo "changed" ;;
  3) echo "nothing to do" ;;
  2) echo "usage error — fix the command line" >&2; exit 2 ;;
  *) echo "failed" >&2; exit 1 ;;
esac
```

Note that `$?` must be captured before any other command runs, and that
`set -e` will abort on `3` as well — use `if sr …; then` or `|| true` when "no
match" is an acceptable outcome.

## Per-command behaviour

| Command | `0` | `3` | other |
| --- | --- | --- | --- |
| `replace` | at least one file changed (or would change, with `--dry-run`) | nothing matched | `1` write/session error · `2` usage |
| `find` | at least one match | no match | `2` usage |
| `diff` | at least one difference | no difference | `2` usage |
| `rename` | at least one rename executed (or planned, with `--dry-run`) | nothing to rename | `1` error · `2` usage |
| `rollback` | everything requested was restored | — | `4` at least one file could not be restored · `1` unknown session |
| `sessions` | the action succeeded | — | `2` usage (e.g. `delete` without `--yes`) |
| `update` | up to date, or the update was installed | — | `5` network/checksum/permission failure |
| `doctor` | every check passed | — | `1` at least one check failed |
| `selftest` | every check passed | — | `6` at least one check failed |
| `config` | the action succeeded | — | `2` invalid key, value or file |
| `completions` | script printed or installed | — | `2` unsupported shell |
| `help` | topic rendered | — | `2` unknown topic |

## See also

```sh
sr help exit-codes      # the same table, in the terminal
sr help json            # the --json document, including .exit_code
man sr                  # EXIT STATUS section
```
