Exit Codes

POSIX exit codes and common conventions.

Standard Unix Exit Codes

Basic Codes (POSIX):

Code Meaning

0

Success / True

1

General error / False

2

Misuse of shell command (syntax error, invalid option)

Reserved Exit Codes (Bash):

Code Meaning

126

Command invoked but not executable (permission denied)

127

Command not found

128

Invalid exit argument (exit takes only integers 0-255)

128+n

Fatal error, killed by signal n

130

Script terminated by Ctrl+C (128 + 2 = SIGINT)

137

Killed by SIGKILL (128 + 9)

139

Segmentation fault (128 + 11 = SIGSEGV)

141

Broken pipe (128 + 13 = SIGPIPE)

143

Terminated by SIGTERM (128 + 15)

255

Exit status out of range (code modulo 256)

BSD sysexits.h Standard:

Code Meaning

64

EX_USAGE - Command line usage error

65

EX_DATAERR - Data format error

66

EX_NOINPUT - Cannot open input

67

EX_NOUSER - Addressee unknown

68

EX_NOHOST - Host name unknown

69

EX_UNAVAILABLE - Service unavailable

70

EX_SOFTWARE - Internal software error

71

EX_OSERR - System error (e.g., can’t fork)

72

EX_OSFILE - Critical OS file missing

73

EX_CANTCREAT - Can’t create output file

74

EX_IOERR - Input/output error

75

EX_TEMPFAIL - Temporary failure; retry

76

EX_PROTOCOL - Remote protocol error

77

EX_NOPERM - Permission denied

78

EX_CONFIG - Configuration error

Unix Signal Numbers

Common Signals (used in 128+n codes):

Signal Number Description

SIGHUP

1

Hangup (terminal closed)

SIGINT

2

Interrupt (Ctrl+C)

SIGQUIT

3

Quit (Ctrl+\, core dump)

SIGILL

4

Illegal instruction

SIGTRAP

5

Trace/breakpoint trap

SIGABRT

6

Abort signal (assert failed)

SIGBUS

7

Bus error

SIGFPE

8

Floating point exception

SIGKILL

9

Kill (cannot be caught)

SIGUSR1

10

User-defined signal 1

SIGSEGV

11

Segmentation fault

SIGUSR2

12

User-defined signal 2

SIGPIPE

13

Broken pipe

SIGALRM

14

Alarm clock (timeout)

SIGTERM

15

Termination (graceful)

SIGCHLD

17

Child status changed

SIGCONT

18

Continue if stopped

SIGSTOP

19

Stop (cannot be caught)

SIGTSTP

20

Terminal stop (Ctrl+Z)

Calculate exit code from signal:

# Process killed by signal -> exit code = 128 + signal_number
# Ctrl+C (SIGINT=2): 128 + 2 = 130
# kill -9 (SIGKILL=9): 128 + 9 = 137
# SIGSEGV (11): 128 + 11 = 139

# Decode exit code
exit_code=137
if (( exit_code > 128 )); then
    signal=$((exit_code - 128))
    echo "Killed by signal $signal ($(kill -l $signal))"
fi

Send signals:

kill -SIGTERM $PID    # Graceful termination (default)
kill -9 $PID          # Force kill (SIGKILL)
kill -SIGSTOP $PID    # Pause process
kill -SIGCONT $PID    # Resume process
kill -SIGHUP $PID     # Reload config (convention)
kill -SIGUSR1 $PID    # Custom signal 1

Exit Code Handling in Scripts

Check last command:

# $? contains exit code of last command
some_command
if [[ $? -eq 0 ]]; then
    echo "Success"
else
    echo "Failed with code $?"
fi

# More idiomatic - use command directly
if some_command; then
    echo "Success"
else
    echo "Failed"
fi

# Capture exit code for later use
some_command
status=$?
# ... other commands ...
if [[ $status -ne 0 ]]; then
    echo "Earlier command failed"
fi

Handle specific codes:

some_command
case $? in
    0)   echo "Success" ;;
    1)   echo "General error" ;;
    2)   echo "Invalid arguments" ;;
    126) echo "Permission denied" ;;
    127) echo "Command not found" ;;
    *)   echo "Unknown error: $?" ;;
esac

Exit with appropriate code:

#!/bin/bash

# Exit on first error
set -e

# Exit with usage error
usage() {
    echo "Usage: $0 <arg>" >&2
    exit 64  # EX_USAGE
}

# Validate input
[[ -z "$1" ]] && usage

# Check file exists
if [[ ! -f "$1" ]]; then
    echo "Error: File not found: $1" >&2
    exit 66  # EX_NOINPUT
fi

# Check permissions
if [[ ! -r "$1" ]]; then
    echo "Error: Cannot read: $1" >&2
    exit 77  # EX_NOPERM
fi

# Success
exit 0

Pipeline exit codes:

# ${PIPESTATUS[@]} contains exit codes of all pipeline commands
cat nonexistent | grep foo | wc -l
echo "Exit codes: ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}"
# Output: Exit codes: 1 1 0

# Exit on any pipeline failure (bash)
set -o pipefail
cat nonexistent | grep foo | wc -l  # Now exits with non-zero

# Check if any pipeline command failed
process_data | transform | output
if [[ "${PIPESTATUS[*]}" =~ [^0\ ] ]]; then
    echo "Pipeline had failures"
fi

Subshell exit codes:

# Subshell exit code becomes $?
(
    cd /nonexistent
    echo "This won't run"
)
echo "Subshell exit: $?"  # 1

# Command substitution doesn't set $? (use pipefail)
result=$(failing_command)  # $? is set but easy to miss

# Better: check explicitly
if ! result=$(failing_command); then
    echo "Command failed"
fi

curl Exit Codes

Code Meaning

0

Success

1

Unsupported protocol

2

Failed to initialize

3

URL malformed

5

Couldn’t resolve proxy

6

Couldn’t resolve host

7

Failed to connect to host

22

HTTP page not retrieved (4xx/5xx with -f flag)

23

Write error

26

Read error

28

Operation timed out

35

SSL connect error

47

Too many redirects

51

Peer’s SSL certificate wasn’t OK

52

Server didn’t reply

55

Failed sending network data

56

Failure receiving network data

60

SSL certificate problem (verification failed)

67

Login denied

Usage:

# Use -f to fail on HTTP errors (returns 22)
curl -sf "https://api.example.com/endpoint" || echo "API call failed"

# Get HTTP code without failing
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")

# Combine exit code and HTTP code
response=$(curl -sf -w "\n%{http_code}" "$url")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')

SSH/SCP Exit Codes

Code Meaning

0

Success

1

Generic error

2

Connection failed

65

Host key verification failed

66

Read error (local file)

67

Write error (local file)

68

Network error

69

Protocol error

70

Authentication failed

71

Memory allocation error

72

File error (local)

73

Ctrl+C (user interrupt)

74

Unknown host

75

Connection refused

76

Connection closed

77

Connection lost

78

Permission denied (server)

79

No such file (remote)

255

SSH error (general, often auth fail)

Usage:

# Check SSH connection
ssh -o ConnectTimeout=5 -o BatchMode=yes user@host "exit 0"
case $? in
    0)   echo "Connection successful" ;;
    255) echo "SSH error (auth, connection, or config)" ;;
    *)   echo "Exit code: $?" ;;
esac

# SCP with error handling
scp file.txt user@host:/path/ || {
    echo "SCP failed with code $?"
    exit 1
}

rsync Exit Codes

Code Meaning

0

Success

1

Syntax or usage error

2

Protocol incompatibility

3

Errors selecting input/output files/dirs

4

Requested action not supported

5

Error starting client-server protocol

6

Daemon unable to append to log file

10

Error in socket I/O

11

Error in file I/O

12

Error in rsync protocol data stream

13

Errors with program diagnostics

14

Error in IPC code

20

Received SIGUSR1 or SIGINT

21

Waitpid() error

22

Memory allocation error

23

Partial transfer (some files not transferred)

24

Partial transfer (source file vanished)

25

Limit reached (--max-delete)

30

Timeout in data send/receive

35

Timeout waiting for daemon connection

Usage:

# Handle partial transfer
rsync -avz source/ dest/
exit_code=$?
if [[ $exit_code -eq 0 ]]; then
    echo "Complete success"
elif [[ $exit_code -eq 23 || $exit_code -eq 24 ]]; then
    echo "Partial transfer (some files missing/vanished)"
else
    echo "rsync failed: $exit_code"
fi

Git Exit Codes

Code Meaning

0

Success

1

Generic error / false result (e.g., diff found changes)

128

Fatal error (usually with error message)

129

Invalid options

Common scenarios:

# git diff returns 1 if differences found
git diff --quiet HEAD
if [[ $? -eq 0 ]]; then
    echo "No changes"
else
    echo "Working tree has changes"
fi

# Check if branch exists
git rev-parse --verify --quiet branch-name
[[ $? -eq 0 ]] && echo "Branch exists"

# Check if repo is clean
if git diff-index --quiet HEAD --; then
    echo "Clean"
else
    echo "Dirty"
fi

# Check if inside git repo
git rev-parse --is-inside-work-tree &>/dev/null
[[ $? -eq 0 ]] && echo "In git repo"

grep Exit Codes

Code Meaning

0

Match found (one or more lines)

1

No match found

2

Error (file not found, permission, syntax)

Usage:

# Check for pattern without output
if grep -q "pattern" file.txt; then
    echo "Found"
fi

# Differentiate no match from error
grep "pattern" file.txt
case $? in
    0) echo "Found matches" ;;
    1) echo "No matches" ;;
    2) echo "Error occurred" ;;
esac

# Suppress errors, only check for matches
if grep -sq "pattern" file.txt 2>/dev/null; then
    echo "Found (ignoring errors)"
fi

Test/[ ] Exit Codes

Code Meaning

0

True (condition met)

1

False (condition not met)

2

Error (syntax, invalid args)

Common tests:

# File tests
[[ -e file ]]    # Exists
[[ -f file ]]    # Regular file
[[ -d dir ]]     # Directory
[[ -L link ]]    # Symbolic link
[[ -r file ]]    # Readable
[[ -w file ]]    # Writable
[[ -x file ]]    # Executable
[[ -s file ]]    # Size > 0
[[ -n "$var" ]]  # String not empty
[[ -z "$var" ]]  # String is empty

# Comparisons
[[ $a -eq $b ]]  # Numeric equal
[[ $a -ne $b ]]  # Numeric not equal
[[ $a -lt $b ]]  # Less than
[[ $a -gt $b ]]  # Greater than
[[ "$a" == "$b" ]]  # String equal
[[ "$a" != "$b" ]]  # String not equal
[[ "$a" =~ regex ]]  # Regex match

# Check exit code
[[ $? -eq 0 ]] && echo "Last command succeeded"

HTTP Status Codes

1xx Informational:

Code Meaning

100

Continue

101

Switching Protocols

102

Processing (WebDAV)

2xx Success:

Code Meaning

200

OK

201

Created

202

Accepted

204

No Content

206

Partial Content

3xx Redirection:

Code Meaning

301

Moved Permanently

302

Found (Moved Temporarily)

303

See Other

304

Not Modified

307

Temporary Redirect

308

Permanent Redirect

4xx Client Error:

Code Meaning

400

Bad Request

401

Unauthorized (auth required)

403

Forbidden (auth insufficient)

404

Not Found

405

Method Not Allowed

408

Request Timeout

409

Conflict

410

Gone (permanently)

413

Payload Too Large

415

Unsupported Media Type

422

Unprocessable Entity

429

Too Many Requests (rate limit)

5xx Server Error:

Code Meaning

500

Internal Server Error

501

Not Implemented

502

Bad Gateway

503

Service Unavailable

504

Gateway Timeout

507

Insufficient Storage

Handling in scripts:

# Check HTTP status
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")

case $http_code in
    2??) echo "Success: $http_code" ;;
    3??) echo "Redirect: $http_code" ;;
    401) echo "Auth required" ;;
    403) echo "Forbidden" ;;
    404) echo "Not found" ;;
    4??) echo "Client error: $http_code" ;;
    5??) echo "Server error: $http_code" ;;
    *)   echo "Unknown: $http_code" ;;
esac

kubectl Exit Codes

Code Meaning

0

Success

1

General error / failure

126

Command cannot execute (permission)

127

Command not found

137

Killed (OOM or manual)

Usage:

# Check if resource exists
kubectl get deployment myapp &>/dev/null
[[ $? -eq 0 ]] && echo "Deployment exists"

# Wait with timeout
if ! kubectl wait --for=condition=ready pod -l app=myapp --timeout=60s; then
    echo "Pods not ready after 60s"
    exit 1
fi

# Apply with error handling
if kubectl apply -f manifest.yaml; then
    echo "Applied successfully"
else
    echo "Apply failed"
    kubectl get events --sort-by=.lastTimestamp | tail -5
fi

Vault Exit Codes

Code Meaning

0

Success

1

Error (with message)

2

Usage error (invalid args/options)

Usage:

# Check Vault status
if vault status &>/dev/null; then
    echo "Vault is unsealed and accessible"
else
    echo "Vault is sealed or unreachable"
fi

# Read secret with error handling
if ! secret=$(vault kv get -format=json secret/mypath 2>/dev/null); then
    echo "Failed to read secret"
    exit 1
fi

# Check if secret exists
vault kv get -format=json secret/mypath &>/dev/null
[[ $? -eq 0 ]] && echo "Secret exists"

Docker Exit Codes

Container Exit Codes:

Code Meaning

0

Success

1

Application error

125

Docker daemon error

126

Command cannot invoke (permission)

127

Command not found in container

128+n

Container received signal n

137

OOM killed or docker kill (SIGKILL)

139

Segmentation fault

143

Graceful shutdown (SIGTERM)

255

Exit status out of range

Usage:

# Run with exit code capture
docker run myimage mycommand
exit_code=$?

case $exit_code in
    0)   echo "Success" ;;
    125) echo "Docker daemon error" ;;
    126) echo "Cannot invoke command (permission)" ;;
    127) echo "Command not found in container" ;;
    137) echo "OOM killed or docker kill" ;;
    143) echo "Graceful shutdown" ;;
    *)   echo "Exit code: $exit_code" ;;
esac

# Get last container exit code
docker inspect --format='{{.State.ExitCode}}' container_name

# Check container health
docker inspect --format='{{.State.Health.Status}}' container_name

Ansible Exit Codes

Code Meaning

0

Success (all hosts OK)

1

Error (syntax, config)

2

One or more hosts failed

3

One or more hosts unreachable

4

Parser error

5

Bad or incomplete options

99

User interrupted

250

Unexpected error

Usage:

# Run playbook with error handling
ansible-playbook site.yml
exit_code=$?

case $exit_code in
    0) echo "All hosts OK" ;;
    2) echo "Some hosts failed" ;;
    3) echo "Some hosts unreachable" ;;
    *) echo "Ansible error: $exit_code" ;;
esac

# Retry unreachable hosts
ansible-playbook site.yml --limit @site.retry

Terraform Exit Codes

Code Meaning

0

Success (apply/plan with no changes)

1

Error (config, apply failed)

2

Plan has changes (with -detailed-exitcode)

Usage:

# Check if plan has changes
terraform plan -detailed-exitcode
case $? in
    0) echo "No changes" ;;
    1) echo "Error" ;;
    2) echo "Changes needed" ;;
esac

# Apply with handling
if terraform apply -auto-approve; then
    echo "Apply successful"
else
    echo "Apply failed"
    exit 1
fi

Python Exit Codes

Code Meaning

0

Success (sys.exit(0) or normal termination)

1

Error (sys.exit(1) or uncaught exception)

2

Command line syntax error

Setting exit codes:

#!/usr/bin/env python3
import sys

# Exit success
sys.exit(0)

# Exit error
sys.exit(1)

# Exit with message (goes to stderr, code 1)
sys.exit("Error: something went wrong")

# In scripts
def main():
    if error_condition:
        print("Error occurred", file=sys.stderr)
        return 1
    return 0

if __name__ == "__main__":
    sys.exit(main())

Checking in shell:

python script.py
[[ $? -eq 0 ]] && echo "Python script succeeded"

netapi Exit Codes

Code Meaning

0

Success

1

General error (API error, invalid input)

2

Invalid arguments / usage error

Usage:

# Check ISE connection
if netapi ise mnt sessions &>/dev/null; then
    echo "ISE API accessible"
fi

# Handle specific errors
netapi ise ers endpoint-by-mac "$mac" 2>/dev/null
case $? in
    0) echo "Endpoint found" ;;
    1) echo "Not found or API error" ;;
    2) echo "Invalid MAC format" ;;
esac

Exit Code Best Practices

Script template:

#!/bin/bash
set -euo pipefail  # Exit on error, undefined var, pipe fail

# Constants
readonly EX_OK=0
readonly EX_USAGE=64
readonly EX_DATAERR=65
readonly EX_NOINPUT=66
readonly EX_SOFTWARE=70
readonly EX_TEMPFAIL=75
readonly EX_NOPERM=77
readonly EX_CONFIG=78

# Usage
usage() {
    echo "Usage: $0 [-h] <input_file>" >&2
    exit $EX_USAGE
}

# Error handler
die() {
    echo "Error: $1" >&2
    exit "${2:-1}"
}

# Cleanup on exit
cleanup() {
    rm -f "$TMPFILE" 2>/dev/null || true
}
trap cleanup EXIT

# Main
main() {
    [[ $# -lt 1 ]] && usage

    local input="$1"
    [[ -f "$input" ]] || die "File not found: $input" $EX_NOINPUT
    [[ -r "$input" ]] || die "Cannot read: $input" $EX_NOPERM

    # Process...

    return $EX_OK
}

main "$@"

Wrapper functions:

# Run command and return success/fail
check_success() {
    "$@" &>/dev/null
}

# Run command, log on failure
run_or_fail() {
    if ! "$@"; then
        echo "Command failed: $*" >&2
        return 1
    fi
}

# Retry command with backoff
retry() {
    local max_attempts="${1:-3}"
    shift
    local attempt=1

    while [[ $attempt -le $max_attempts ]]; do
        if "$@"; then
            return 0
        fi
        echo "Attempt $attempt failed, retrying..." >&2
        sleep $((attempt * 2))
        ((attempt++))
    done

    echo "All $max_attempts attempts failed" >&2
    return 1
}

# Usage
retry 3 curl -sf "$url"