Exit Statuses as a Shell API
When a command-line program finishes, it hands back an integer between 0 and 255 to the calling shell. That value, inspectable with echo $?, is one of the primary channels through which scripts and shells interpret what happened inside the process they launched. The conventions around those numbers are loose but widely understood: 0 means success, 1 means a generic failure, and 2 often indicates incorrect usage.
The ambiguity begins when a program wants to signal something more specific than "it broke." For example, a process killed by a fatal signal returns 128 + n, where n is the signal number. A SIGINT (signal 2, i.e., Ctrl+C) therefore surfaces as an exit status of 130:
$ curl -n https://api.heroku.com/apps
^C
$ echo $?
130
There is no official registry for custom exit codes, but two sources offer rough guidance. The Advanced Bash-scripting Guide catalogs a set of reserved codes, and the kernel header sysexits.h attempts a standardization for C and C++ programs:
#define EX_OK 0 /* successful termination */
#define EX__BASE 64 /* base value for error messages */
#define EX_USAGE 64 /* command line usage error */
#define EX_DATAERR 65 /* data format error */
#define EX_NOINPUT 66 /* cannot open input */
#define EX_NOUSER 67 /* addressee unknown */
#define EX_NOHOST 68 /* host name unknown */
#define EX_UNAVAILABLE 69 /* service unavailable */
#define EX_SOFTWARE 70 /* internal software error */
#define EX_OSERR 71 /* system error (e.g., can't fork) */
#define EX_OSFILE 72 /* critical OS file missing */
#define EX_CANTCREAT 73 /* can't create (user) output file */
#define EX_IOERR 74 /* input/output error */
#define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */
#define EX_PROTOCOL 76 /* remote error in protocol */
#define EX_NOPERM 77 /* permission denied */
#define EX_CONFIG 78 /* configuration error */
#define EX__MAX 78 /* maximum listed value */
Choosing a Code for a 2FA Challenge
This question came up while building hk, a Heroku CLI client, during a discussion about two-factor authentication. The tool's design philosophy is that most commands should be non-interactive by default, with a few exceptions like hk login. That makes it safe to compose hk commands into shell scripts without worrying that a prompt will unexpectedly block automation.
With 2FA added to the Heroku API, a two-factor code could now be required on any given call, depending on the endpoint and the sensitivity of the operation. An interactive client can simply prompt the user mid-call, but a non-interactive tool has different constraints. The chosen behavior in hk is to fail with a well-known exit status that explicitly marks the failure as a 2FA problem. That way, a script can detect the condition and take a reasonable follow-up action, such as notifying an operator that a fresh code is needed.
The harder part was picking the number. The Advanced Bash-scripting Guide makes a practical proposal: restrict user-defined codes to the range 64–113, leaving 0 for success. That range stays clear of the reserved codes and the sysexits.h assignments (which generally land below 79). Starting at 79, after skipping the sysexits.h entries, yields a sensible lower bound for application-specific statuses. That is the convention adopted in hk's implementation:
$ hk env -a paranoid
error: A second authentication factor or pre-authorization is required
for this request. Your account has either two-factor or a Yubikey
registered. Authorize with `hk authorize`.
$ echo $?
79
For a tool that wants to distinguish failure classes reliably, staying within a bounded, documented range beats inventing arbitrary numbers. It keeps the exit-status contract readable and gives script authors a dependable way to branch on the cause rather than just the fact of a failure.



