Posted on
Artificial Intelligence

Generating Error Handling Logic Using Artificial Intelligence

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Generating Error Handling Logic Using Artificial Intelligence (for Bash)

When bash scripts fail, they often fail silently. One missing directory, one unchecked return code, and your “quick script” becomes an overnight fire drill. What if you could use AI to scaffold strong, consistent error handling—fast—then enforce it with static analysis and lightweight tests?

This post shows how to combine AI-assisted code generation with proven Bash safety patterns so your scripts fail loudly, log clearly, and recover gracefully. You’ll get:

  • Why AI is a good fit for error-handling scaffolds

  • A minimal, reusable Bash error-handling module

  • A safe workflow to generate, validate, and integrate AI-suggested logic

  • Real-world examples (retry logic, cleanup traps, and more)

  • Package installation commands for apt, dnf, and zypper


Why AI for Bash error handling?

  • Bash error handling is 80% patterns and boilerplate. LLMs are good at pattern-heavy scaffolding (traps, retries, logging wrappers).

  • Humans are still essential for intent and context; pair AI with guardrails like shellcheck, bash -n, and smoke tests to keep quality high.

  • Consistency scales. AI helps you standardize how your team handles errors across many scripts (same log shape, same trap discipline, same retry).


Prerequisites and installation

We’ll use:

  • curl: talk to an API

  • jq: extract AI output from JSON

  • shellcheck: lint and catch pitfalls

  • bats (optional): quick test harness

Install with your package manager:

  • Debian/Ubuntu (apt)

    sudo apt update
    sudo apt install -y curl jq shellcheck bats
    
  • Fedora/RHEL/CentOS Stream (dnf)

    sudo dnf install -y curl jq ShellCheck bats
    
  • openSUSE (zypper)

    sudo zypper refresh
    sudo zypper install -y curl jq ShellCheck bats
    

Note: Package name for ShellCheck is lowercase on apt and capitalized on dnf/zypper.


Core content: 5 steps to AI-powered, robust Bash

1) Start with a reusable error-handling module

Create errors.sh you can source from any script. This module:

  • Enforces strict modes

  • Installs a detailed ERR trap

  • Provides logging, die, retry, and required-commands checks

  • Handles cleanup on EXIT, INT, TERM

# errors.sh
# Reusable error handling utilities for Bash scripts.
# Source this file near the top of your script.

# shellcheck shell=bash
if [[ -n "${__ERRORS_SH:-}" ]]; then
  return 0
fi
readonly __ERRORS_SH=1

# Strict modes
set -Eeuo pipefail
shopt -s inherit_errexit || true   # Bash >=4.4, ignore if not available
IFS=$'\n\t'

# Basic logging (levels: INFO, WARN, ERROR)
log() {
  local level="$1"; shift
  local ts
  ts="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
  printf '%s [%s] %s\n' "$ts" "$level" "$*" >&2
}

die() {
  local code="${1:-1}"; shift || true
  log ERROR "${*:-Unspecified error}"
  exit "$code"
}

# Report detailed error context
_err_report() {
  local exit_code="$1"
  # Use arrays for call stack info
  local src="${BASH_SOURCE[1]:-?}"
  local line="${BASH_LINENO[0]:-?}"
  local func="${FUNCNAME[2]:-main}"
  log ERROR "Command: ${BASH_COMMAND}"
  log ERROR "Location: ${src}:${line} in ${func}()"
  log ERROR "Exit code: ${exit_code}"
}

# Trap errors globally (inherited to functions via -E)
trap '_err_report "$?"' ERR

# Cleanup trap (override in your script if needed)
_cleanup() {
  # Put idempotent cleanup here; scripts may override.
  :
}
trap '_cleanup' EXIT INT TERM

# Require necessary commands before proceeding
require_cmds() {
  local missing=0
  for c in "$@"; do
    command -v "$c" >/dev/null 2>&1 || { log ERROR "Missing required command: $c"; missing=1; }
  done
  (( missing == 0 )) || die 127 "Install missing dependencies and retry."
}

# Simple retry with exponential backoff
with_retry() {
  local attempts="${1:-3}"
  local base_delay="${2:-1}"
  shift 2
  local n=1
  until "$@"; do
    local rc=$?
    if (( n >= attempts )); then
      log ERROR "Retry failed after ${attempts} attempts (last rc=$rc) for: $*"
      return "$rc"
    fi
    local delay=$(( base_delay * 2 ** (n-1) ))
    log WARN "Attempt $n failed (rc=$rc). Retrying in ${delay}s: $*"
    sleep "$delay"
    ((n++))
  done
}

# Safer mktemp directory helper
mktempdir() {
  mktemp -d 2>/dev/null || die 1 "mktemp failed"
}

Use it like this in your script:

#!/usr/bin/env bash
# example.sh

# Load error module early
source "./errors.sh"

require_cmds curl jq

main() {
  log INFO "Starting job"
  with_retry 5 1 curl -fsS https://example.com/api | jq -r '.status' >/dev/null
  log INFO "Job finished OK"
}

# Only run main if executed directly, not when sourced
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi

Why it works:

  • set -Eeuo pipefail: aborts on error, undefined variables, and failed pipelines

  • ERR trap with call-site info: you see the failing command, file, and line

  • Retry wrapper prevents transient network blips from crashing your script

  • require_cmds fails fast with actionable messages


2) Use AI to generate context-specific guards and wrappers

Let AI draft boilerplate guards tailored to your task (e.g., argument validation, preflight checks, checksum verification). Keep the prompt focused and ask for POSIX/Bash-4 compatible constructs.

Example prompt template:

You are generating robust Bash error-handling helpers to drop into a script that:

- Downloads an artifact from a URL

- Verifies its SHA256 checksum

- Extracts it to a target directory

Requirements:

- Bash (not zsh), strict modes assumed

- Use functions: validate_args, check_writable_dir, download_artifact, verify_sha256

- No external deps beyond curl, sha256sum, tar

- Return non-zero on failures; do not exit in helpers (let caller decide)

- Log to stderr with clear, single-line messages

- Avoid command substitutions that mask exit codes in pipelines

Output only Bash code, no commentary.

Calling an OpenAI-compatible endpoint with curl and jq (generic; works with many hosted or self-hosted APIs):

# Environment:
#   LLM_API_URL: e.g., https://api.openai.com
#   LLM_API_KEY: your token
#   MODEL: e.g., gpt-4o-mini

curl -sS "${LLM_API_URL}/v1/chat/completions" \
  -H "Authorization: Bearer ${LLM_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"${MODEL}"'",
    "messages": [
      {"role": "system", "content": "You write secure, portable Bash."},
      {"role": "user", "content": "'"$(printf '%s' "$(cat prompt.txt)" | sed 's/"/\\"/g')"'"}
    ],
    "temperature": 0.2
  }' \
| jq -r '.choices[0].message.content' > generated_helpers.sh

Now you have generated_helpers.sh you can source after errors.sh.


3) Validate generated Bash before you trust it

Never paste AI output straight into production. Validate it like any other code:

  • Syntax check:

    bash -n generated_helpers.sh
    
  • ShellCheck with errors only:

    shellcheck -S error generated_helpers.sh
    
  • Quick smoke test with bats (optional): Create test_generated.bats:

    #!/usr/bin/env bats
    
    setup() {
    source "./errors.sh"
    source "./generated_helpers.sh"
    }
    
    @test "validate_args fails on missing URL" {
    run validate_args "" "/tmp" "abc123"
    [ "$status" -ne 0 ]
    }
    

    Run:

    bats test_generated.bats
    

CI tip: Fail the pipeline if shellcheck or bash -n fail. Keep temperature low in the generation step for deterministic output.


4) Compose patterns into a real script

Here’s a compact example that downloads, verifies, and extracts an archive with retries and cleanup. It combines errors.sh + AI-generated helpers (or your own):

#!/usr/bin/env bash
# fetch_and_extract.sh
source "./errors.sh"
source "./generated_helpers.sh"  # assumes it defines: validate_args, download_artifact, verify_sha256, extract_to

ARTIFACT_URL="${1:-}"
TARGET_DIR="${2:-/opt/myapp}"
EXPECTED_SHA256="${3:-}"

_cleanup() {
  # Remove temp dir if it was created
  if [[ -n "${TMPDIR_FAE:-}" && -d "${TMPDIR_FAE}" ]]; then
    rm -rf -- "${TMPDIR_FAE}"
  fi
}
export -f _cleanup  # ensure trap sees it

main() {
  require_cmds curl sha256sum tar

  validate_args "${ARTIFACT_URL}" "${TARGET_DIR}" "${EXPECTED_SHA256}" \
    || die 2 "Invalid arguments. Usage: $0 <url> <target_dir> <sha256>"

  check_writable_dir "${TARGET_DIR}" || die 3 "Target not writable: ${TARGET_DIR}"

  TMPDIR_FAE="$(mktempdir)"
  local outfile="${TMPDIR_FAE}/artifact.tar.gz"
  log INFO "Downloading: ${ARTIFACT_URL}"

  with_retry 4 2 download_artifact "${ARTIFACT_URL}" "${outfile}" \
    || die 10 "Download failed"

  verify_sha256 "${outfile}" "${EXPECTED_SHA256}" \
    || die 11 "Checksum mismatch"

  extract_to "${outfile}" "${TARGET_DIR}" \
    || die 12 "Extraction failed"

  log INFO "Done -> ${TARGET_DIR}"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi

If the AI didn’t produce check_writable_dir, write it or prompt for it explicitly. Keep helpers small and composable.


5) Close the loop: let runtime errors improve your script

Use real failures to drive future improvements. Capture logs, redact secrets, then ask the AI for hardening suggestions.

  • Example sanitized log snippet:

    2026-07-06T12:01:09Z [INFO] Downloading: https://cdn.example.com/app.tar.gz
    2026-07-06T12:01:12Z [WARN] Attempt 1 failed (rc=22). Retrying in 1s: curl -fsS ...
    2026-07-06T12:01:14Z [WARN] Attempt 2 failed (rc=22). Retrying in 2s: curl -fsS ...
    2026-07-06T12:01:21Z [ERROR] Retry failed after 4 attempts (last rc=22) for: curl -fsS ...
    2026-07-06T12:01:21Z [ERROR] Command: curl -fsS ...
    2026-07-06T12:01:21Z [ERROR] Location: fetch_and_extract.sh:41 in main()
    2026-07-06T12:01:21Z [ERROR] Exit code: 22
    
  • Improvement prompt idea:

    The download intermittently fails with rc=22. Propose a curl-based download helper
    that:
    - Adds random jitter to backoff
    - Switches to a mirror on HTTP 404/5xx
    - Validates expected content-type
    - Exposes configurable timeouts via env vars
    Output Bash only; keep dependencies to curl and awk.
    

Validate, integrate, repeat.


Real-world snippet: safer curl download with jitter and content-type check

Ask your AI for something like this, then lint and test it:

# Requires: curl, awk
download_artifact() {
  local url="$1" out="$2"
  : "${CURL_TIMEOUT:=15}"
  : "${CURL_CONNECT_TIMEOUT:=5}"
  : "${ACCEPT_CT:=application/gzip}"

  # Write to temp then move to avoid partials
  local tmp="${out}.part"
  rm -f -- "$tmp"

  # -f: fail on HTTP errors; -S: show errors; -s: silent progress
  curl -fSsv \
    --connect-timeout "$CURL_CONNECT_TIMEOUT" \
    --max-time "$CURL_TIMEOUT" \
    -H "Accept: ${ACCEPT_CT}" \
    -o "$tmp" "$url" 2> >(awk '{print strftime("%Y-%m-%dT%H:%M:%SZ"), "[CURL]", $0 >> "/dev/stderr"}') \
    || return $?

  # Basic content-type check via HEAD request
  local ct
  ct="$(curl -fsSI -H "Accept: ${ACCEPT_CT}" "$url" | awk -F': ' 'tolower($1)=="content-type"{print tolower($2)}' | tr -d '\r')"
  [[ "$ct" == *"${ACCEPT_CT}"* ]] || { log ERROR "Unexpected content-type: ${ct:-unknown}"; return 65; }

  mv -f -- "$tmp" "$out"
}

# Jittered retry wrapper example:
with_retry_jitter() {
  local attempts="${1:-5}" base="${2:-1}"; shift 2
  local n=1
  until "$@"; do
    local rc=$?
    if (( n >= attempts )); then
      log ERROR "Exhausted ${attempts} attempts (rc=$rc) for: $*"
      return "$rc"
    fi
    # jitter in [0, base]
    local jitter
    jitter="$(awk -v b="$base" 'BEGIN{srand(); printf "%.3f", rand()*b}')"
    local delay
    delay=$(awk -v n="$n" -v b="$base" -v j="$jitter" 'BEGIN{printf "%.3f", (b * (2^(n-1))) + j}')
    log WARN "Attempt $n failed (rc=$rc). Retrying in ${delay}s: $*"
    sleep "$delay"
    ((n++))
  done
}

Swap this into your script’s download path and observe clearer diagnostics and more resilient transfers.


Common pitfalls AI can help you avoid

  • Forgetting set -o pipefail: pipelines masking failures

  • Writing helpers that exit instead of returning non-zero: hard to compose, breaks libraries

  • Traps that don’t include location/command: you get “something failed” instead of “what, where, and why”

  • Unchecked external dependencies: scripts run fine on your box, fail elsewhere

  • Partial writes: not using *.part then mv for atomic-ish file updates

Prompt your AI to target these pitfalls explicitly in its output.


Conclusion and next steps

You don’t need to handcraft every trap, retry, and log line. Let AI generate the error-handling scaffolds, then put them through a simple pipeline: bash -n → shellcheck → bats. Combine the generated helpers with a small, battle-tested module like errors.sh, and you’ll get Bash that fails fast, explains itself, and recovers when it can.

Call to action:

  • Add errors.sh to your repo and standardize on its patterns

  • Wire a “generate → validate → integrate” job into CI

  • Start with one high-impact script this week (backups, deploys, data sync), and harden it using the flow above

If you want a follow-up post with a ready-made Makefile and CI snippets for this pipeline (including apt/dnf/zypper setup steps), say the word.