A shell reports success the way the operating system has for decades: a process that completes correctly exits with status 0 per the Linux convention described on Stack Overflow. Everything else is a failure signal. That single integer is the cheapest channel you have to an agent, because the agent reads $? and branches on it before it ever parses text.
The problem is that the byte is overloaded. Code 1 is a catchall for general errors, 2 signals misuse of a shell builtin, 126 means the command cannot be executed, 127 means not found, and 128+n reports a fatal signal n per the Advanced Bash Scripting Guide appendix. Those meanings belong to the shell and the kernel, not to your program. If your tool returns 127 because "the record you asked for is gone," the calling agent cannot distinguish your domain error from a missing binary.
The widely available sysexits.h scheme was an attempt at a standard, but practitioners have long argued that its categories are ambiguous enough that callers cannot reliably depend on them as discussed on Hacker News. Read that thread as a warning: a code table only works if you own it, document it, and keep it small enough that an agent can hold the whole map in view.
Note also the arithmetic trap. Exit values wrap modulo 256, so exit 3809 surfaces as 225 per the same appendix. Never compute a code from a variable you have not bounded.



