# Exit status **Never call `exit()` with an akerr status. Call `akerr_exit()`.** ```c void akerr_exit(int status); ``` This applies everywhere you are leaving the process on account of a status, not just in an unhandled-error handler: a CLI's top-level `HANDLE` block, an initialization routine that cannot continue, a `main()` that ends by reporting the status it finished with. One function owns the mapping so that a given status produces the same exit code no matter which of your exits it left by. The mapping exists because a process exit status is one byte wide. `exit()` accepts an `int` and the kernel keeps the low 8 bits of it — `_exit()`, `_Exit()`, `quick_exit()` and the raw `exit_group` syscall all behave identically, and even `waitid()`, whose `si_status` is a full `int`, sees the truncated value because the truncation happened before the parent looked. There is no wider `exit()` to reach for. That leaves 0 through 255 as the only statuses an exit code can carry, and consumer statuses start at `AKERR_FIRST_CONSUMER_STATUS` (256) — so *no* consumer status can be an exit code: ``` status exit code 0 0 (success) 1 .. 255 the status negative, or > 255 AKERR_EXIT_STATUS_UNREPRESENTABLE (125) ``` Passing the low byte instead would have made status 256 exit 0 and report success to the shell, and status 300 exit 44 — an unrelated error's code. 125 is the conventional "the tool itself failed" status; 126, 127 and 128+*n* already belong to the shell. Status 0 exits 0, because 0 is this library's success status. That is not a hole in the rule that an unhandled error never exits 0: `PROCESS` opens with `case 0`, which marks a zero status handled, so a successful context cannot reach `FINISH_NORETURN`'s call to the handler at all. Above 255, the exit code tells you the process died of an error, not which one. 125 is inside the library's reserved band and so is also some host's `errno`, and every status above 255 collapses onto it. **The stack trace is what identifies the error** — it is printed before the handler runs, carrying the status at full width along with its registered name. ## Replacing the handler After the trace is printed, `FINISH_NORETURN` calls `akerr_handler_unhandled_error`. The default implementation, `akerr_default_handler_unhandled_error()`, hands `errctx->status` to `akerr_exit()` (a NULL context exits 1). Replace it if you need something else to happen first — a core dump, a crash reporter, a flush — and finish by calling `akerr_exit()` so the exit code still means what it means everywhere else: ```c static void mylib_handler(akerr_ErrorContext *e) { mylib_flush_telemetry(); if ( e == NULL ) { akerr_exit(AKERR_API); } akerr_exit(e->status); } akerr_handler_unhandled_error = &mylib_handler; ``` `akerr_exit()` is declared `AKERR_NORETURN`, so the compiler knows a handler ending in one of those calls is complete rather than falling off the end. Set the handler once, before you start any threads. `tests/err_custom_handler.c` installs one that does not exit at all, which is how the test suite asserts on unhandled errors without dying.