shell-error-handling

$npx mdskill add TheBushidoCollective/han/shell-error-handling

Implement robust shell scripting by managing exit codes, traps, and cleanup routines.

  • Ensures scripts reliably clean up resources and handle unexpected failures gracefully.
  • Leverages core shell utilities like Bash, Read, Write, and Write for execution.
  • Guides the agent to correctly structure error checks and resource management blocks.
  • Provides executable code snippets demonstrating best practices for shell scripting.

SKILL.md

.github/skills/shell-error-handlingView on GitHub ↗
---
name: shell-error-handling
user-invocable: false
description: Use when implementing error handling, cleanup routines, or debugging in shell scripts. Covers traps, exit codes, and robust error patterns.
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Grep
  - Glob
---

# Shell Error Handling

Patterns for robust error handling, cleanup, and debugging in shell scripts.

## Exit Codes

### Standard Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error |
| 2 | Misuse of shell command |
| 126 | Command not executable |
| 127 | Command not found |
| 128+N | Fatal signal N |
| 130 | Ctrl+C (SIGINT) |

### Checking Exit Status

```bash
# Check last command's exit status
if ! command; then
    echo "Command failed with status $?" >&2
    exit 1
fi

# Alternative pattern
command || {
    echo "Command failed" >&2
    exit 1
}

# Capture exit status
command
status=$?
if (( status != 0 )); then
    echo "Failed with status $status" >&2
fi
```

## Trap for Cleanup

### Basic Cleanup Pattern

```bash
#!/usr/bin/env bash
set -euo pipefail

cleanup() {
    local exit_code=$?
    # Remove temporary files
    rm -f "$TEMP_FILE" 2>/dev/null || true
    exit "$exit_code"
}

trap cleanup EXIT

TEMP_FILE=$(mktemp)
# Script continues...
# cleanup runs automatically on exit
```

### Handling Multiple Signals

```bash
#!/usr/bin/env bash
set -euo pipefail

cleanup() {
    echo "Cleaning up..." >&2
    rm -rf "$WORK_DIR" 2>/dev/null || true
}

handle_interrupt() {
    echo "Interrupted by user" >&2
    cleanup
    exit 130
}

trap cleanup EXIT
trap handle_interrupt INT TERM

WORK_DIR=$(mktemp -d)
```

### Trap Best Practices

```bash
# Preserve original exit code in cleanup
cleanup() {
    local exit_code=$?
    # Cleanup operations here
    rm -f "$temp_file" 2>/dev/null || true
    # Restore exit code
    exit "$exit_code"
}

# Use || true for optional cleanup
trap 'rm -f "$temp_file" 2>/dev/null || true' EXIT
```

## Error Reporting

### Standard Error Output

```bash
# Always write errors to stderr
echo "Error: Something went wrong" >&2

# Error function
error() {
    echo "Error: $*" >&2
}

# Die function - error and exit
die() {
    echo "Fatal: $*" >&2
    exit 1
}

# Usage
[[ -f "$config" ]] || die "Config file not found: $config"
```

### Verbose Logging

```bash
#!/usr/bin/env bash
set -euo pipefail

VERBOSE="${VERBOSE:-false}"

log() {
    if [[ "$VERBOSE" == "true" ]]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
    fi
}

error() {
    echo "[ERROR] $*" >&2
}

log "Starting script"
log "Processing file: $file"
```

## Defensive Programming

### Check Prerequisites

```bash
# Check required commands exist
require_command() {
    command -v "$1" >/dev/null 2>&1 || {
        echo "Error: Required command '$1' not found" >&2
        exit 1
    }
}

require_command jq
require_command curl
require_command shellcheck
```

### Validate Input

```bash
# Validate arguments
if [[ $# -lt 2 ]]; then
    echo "Usage: $0 <source> <destination>" >&2
    exit 1
fi

source_file="$1"
dest_dir="$2"

# Validate file exists
[[ -f "$source_file" ]] || {
    echo "Error: Source file not found: $source_file" >&2
    exit 1
}

# Validate directory
[[ -d "$dest_dir" ]] || {
    echo "Error: Destination directory not found: $dest_dir" >&2
    exit 1
}
```

### Safe Temporary Files

```bash
# Create secure temp file
TEMP_FILE=$(mktemp) || {
    echo "Error: Failed to create temp file" >&2
    exit 1
}

# Create secure temp directory
TEMP_DIR=$(mktemp -d) || {
    echo "Error: Failed to create temp directory" >&2
    exit 1
}

# Always clean up
trap 'rm -rf "$TEMP_FILE" "$TEMP_DIR" 2>/dev/null || true' EXIT
```

## Debugging

### Debug Mode

```bash
#!/usr/bin/env bash

# Enable debug mode via environment variable
if [[ "${DEBUG:-}" == "1" ]]; then
    set -x
fi

set -euo pipefail

# Or toggle with a flag
while getopts "d" opt; do
    case $opt in
        d) set -x ;;
        *) echo "Usage: $0 [-d]" >&2; exit 1 ;;
    esac
done
```

### Trace Execution

```bash
# Enable tracing for specific section
set -x
problematic_code
set +x

# Trace with custom PS4
export PS4='+ ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
```

## Error Recovery Patterns

### Retry Pattern

```bash
retry() {
    local max_attempts="${1:-3}"
    local delay="${2:-1}"
    shift 2
    local cmd=("$@")

    local attempt=1
    while (( attempt <= max_attempts )); do
        if "${cmd[@]}"; then
            return 0
        fi
        echo "Attempt $attempt failed, retrying in ${delay}s..." >&2
        sleep "$delay"
        (( attempt++ ))
    done

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

# Usage
retry 3 5 curl -f "http://example.com/api"
```

### Fallback Pattern

```bash
# Try primary, fall back to secondary
get_config() {
    if [[ -f "$HOME/.config/myapp/config" ]]; then
        cat "$HOME/.config/myapp/config"
    elif [[ -f "/etc/myapp/config" ]]; then
        cat "/etc/myapp/config"
    else
        echo "Error: No config file found" >&2
        return 1
    fi
}
```

More from TheBushidoCollective/han

SkillDescription
absinthe-resolversUse when implementing GraphQL resolvers with Absinthe. Covers resolver patterns, dataloader integration, batching, and error handling.
absinthe-schemaUse when designing GraphQL schemas with Absinthe. Covers type definitions, interfaces, unions, enums, and schema organization patterns.
absinthe-subscriptionsUse when implementing real-time GraphQL subscriptions with Absinthe. Covers Phoenix channels, PubSub, and subscription patterns.
act-docker-setupUse when configuring Docker environments for act, selecting runner images, managing container resources, or troubleshooting Docker-related issues with local GitHub Actions testing.
act-local-testingUse when testing GitHub Actions workflows locally with act. Covers act CLI usage, Docker configuration, debugging workflows, and troubleshooting common issues when running workflows on your local machine.
act-workflow-syntaxUse when creating or modifying GitHub Actions workflow files. Provides guidance on workflow syntax, triggers, jobs, steps, and expressions for creating valid GitHub Actions workflows that can be tested locally with act.
ameba-configurationUse when configuring Ameba rules and settings for Crystal projects including .ameba.yml setup, rule management, severity levels, and code quality enforcement.
ameba-custom-rulesUse when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
ameba-integrationUse when integrating Ameba into development workflows including CI/CD pipelines, pre-commit hooks, GitHub Actions, and automated code review processes.
analyze-performanceAnalyze performance metrics and identify slow transactions in Sentry