Free — no signup required

Exit Code Hygiene

3 min read

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 by Ctrl+C, which is 128 + 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
Interview Tip

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.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free