The Universal Language of Success and Failure
Every command that runs on a Unix system finishes with an exit code — an integer between 0 and 255 that the operating system passes back to whoever called the program. This is the primary mechanism by which programs communicate success or failure to each other.
- Exit Code
0: Success. The program did what it was asked to do. - Exit Code
1: General failure. Something went wrong. - Exit Code
2: Misuse of the tool — wrong arguments, invalid flags. - Exit Codes
3-125: Application-defined errors. You can use these to signal specific failure modes (e.g.,3= "resource not found",4= "authentication failed"). - Exit Codes
126,127: Reserved by the shell (permission denied, command not found). - Exit Codes
128+: Terminated by a signal (e.g.,130= terminated byCtrl+C, which is128 + SIGINT(2)).
CI/CD pipelines, shell scripts, and orchestration systems rely entirely on exit codes to decide whether to stop or continue. A pipeline step that exits 0 is considered successful; the next step runs. A step that exits non-zero is considered failed; the pipeline stops (or triggers an alert).
The Silent Failure Anti-Pattern:
The most dangerous mistake in CLI engineering is catching an error, printing a message, and then exiting with code 0. Here is what that looks like:
# WRONG — This is a bug, not a feature
try:
result = connect_to_database()
except ConnectionError as e:
print(f"Error: Could not connect to database: {e}")
# Missing: sys.exit(1)
# The script falls through and exits with 0
The CI/CD pipeline sees exit code 0, thinks the database migration succeeded, proceeds to deploy the application, and breaks production. The error message was printed — but nobody was watching, because it was an automated pipeline.
The Correct Pattern:
import sys
try:
result = connect_to_database()
except ConnectionError as e:
print(f"Error: Could not connect to database: {e}", file=sys.stderr)
sys.exit(1) # Non-zero exit: tells the pipeline to stop
Note that the error message goes to stderr (so it doesn't corrupt the data stream) and the exit code is non-zero (so automated systems know to stop).
Defining Specific Exit Codes:
For tools used in automation, document your exit codes. A tool that exits 3 for "resource not found" lets callers distinguish between "the tool crashed" and "the resource doesn't exist yet" — a distinction that matters for retry logic.
# Define exit codes as named constants
EXIT_SUCCESS = 0
EXIT_GENERAL_ERROR = 1
EXIT_USAGE_ERROR = 2
EXIT_NOT_FOUND = 3
EXIT_AUTH_FAILED = 4
Interviewers frequently ask: "How do you make shell scripts or CLI tools reliable in CI/CD pipelines?" Exit codes are the core answer. Specifically, mention the silent failure anti-pattern — catching an exception, printing an error, but exiting 0. This is one of the most common bugs in production automation scripts, and recognizing it signals real operational experience.