← All Posts

How to Do Custom Error Handling on Claude Code

Custom error handling in Claude Code means using hooks, shell exit codes, and structured .claude/settings.json configuration to intercept, log, and recover from failures during agentic tasks. It's essential for developers who run long multi-step workflows and can't afford a silent failure or a mid-task 5-hour usage lockout to derail a PR. Claude Code exposes lifecycle hook points that fire on tool calls, errors, and session events, giving you full control over what happens when something goes wrong.

  • Hooks run as shell commands and communicate back to Claude via exit codes and stdout/stderr
  • Exit code 0 means success; non-zero codes signal an error Claude Code will surface to the model
  • PostToolUse and Stop hook events are the primary interception points for error handling

What is custom error handling in Claude Code?

Claude Code's hooks system lets you attach shell scripts to specific lifecycle events in the agent loop. These hooks receive structured JSON over stdin describing the tool call or event, and return structured feedback that Claude reads before deciding the next step.

Custom error handling builds on this: instead of letting Claude infer failures from noisy terminal output, you write focused hook scripts that classify errors, format clean messages, and optionally halt execution before damage spreads. The result is a tighter feedback loop and fewer wasted tokens chasing red herrings.

According to the Claude Code documentation, hooks are configured per-project in .claude/settings.json and run with the same permissions as your shell user, which means they can read logs, call APIs, send notifications, or write state files.

How to set up a custom error handler with hooks

Step 1: Open your project's settings file

Claude Code reads hook configuration from .claude/settings.json at the root of your project. If the file doesn't exist yet, create it:

mkdir -p .claude && touch .claude/settings.json

Step 2: Register a PostToolUse hook

The PostToolUse event fires after every tool call (Bash, file writes, web fetches, etc.). This is the right place to intercept failures. Add the following to .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/check-bash-error.sh"
          }
        ]
      }
    ]
  }
}

Step 3: Write the handler script

Create .claude/hooks/check-bash-error.sh. The hook receives a JSON payload on stdin containing tool_input, tool_response, and exit status information:

#!/usr/bin/env bash
# Read the JSON payload from Claude Code
INPUT=$(cat)

# Extract the exit code from the tool response
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_response.exit_code // 0')
STDERR_OUT=$(echo "$INPUT" | jq -r '.tool_response.stderr // ""')

if [ "$EXIT_CODE" != "0" ]; then
  # Log the failure for post-session review
  echo "[ERROR] Tool exited with code $EXIT_CODE" >> .claude/error.log
  echo "STDERR: $STDERR_OUT" >> .claude/error.log

  # Return a structured error message to Claude
  echo "Command failed with exit code $EXIT_CODE. Review .claude/error.log. Do not retry automatically."
  exit 2  # Non-zero tells Claude to treat this as a blocking error
fi

exit 0

Exit code 2 signals a hard block: Claude Code will surface the error to the model and pause for human input rather than retrying blindly. This is critical when a bad command could cascade into file corruption or runaway API calls.

Step 4: Handle specific error patterns

You can extend the script to classify errors and respond differently. For example, distinguish network failures from compilation errors:

if echo "$STDERR_OUT" | grep -q "ECONNREFUSED"; then
  echo "Network error: is your dev server running on the expected port?"
  exit 2
fi

if echo "$STDERR_OUT" | grep -q "SyntaxError\|TypeError"; then
  echo "JS runtime error detected. Check the file last written by Claude."
  exit 2
fi

This pattern prevents Claude from entering a retry loop when the real fix requires a human decision, such as starting a stopped service or rolling back a bad migration.

Using the Stop hook for session-level error summaries

The Stop hook fires when Claude Code finishes a session or is interrupted. Use it to aggregate errors from the log file and print a clean summary:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/session-summary.sh"
          }
        ]
      }
    ]
  }
}

The corresponding session-summary.sh can tail the error log and open a diff of changed files, giving you a reviewable artifact before you merge anything.

Custom error handling in multi-agent and subagent tasks

When you use Claude Code's subagent or agent skills, errors in child tasks don't automatically surface to the parent session. Add a PostToolUse hook scoped to the Task tool to catch failures from delegated work:

{
  "matcher": "Task",
  "hooks": [
    {
      "type": "command",
      "command": "bash .claude/hooks/check-task-error.sh"
    }
  ]
}

The hook receives the subagent's result JSON, including any error messages it returned. You can block promotion of a subtask result back to the parent agent until you've validated it.

Slash commands for on-demand error inspection

Beyond hooks, Claude Code's slash commands let you trigger error-checking flows interactively. Common patterns used by developers:

  • /fix-errors: a custom slash command defined in .claude/commands/fix-errors.md that reads the error log and asks Claude to diagnose each entry
  • /usage: the built-in command that shows your current token consumption so you can gauge how much runway you have before hitting a usage reset window
  • /review: runs a structured code review that includes checking for unhandled error paths introduced in the session

You can see the full list of available slash commands in the Claude Code slash commands reference. Custom commands are just Markdown files in .claude/commands/, making them trivially version-controllable alongside your hook scripts.

Preventing usage lockouts during long error-handling sessions

One of the most frustrating failure modes isn't a code error at all: it's hitting your Claude Code usage limit mid-task while you're deep in debugging, triggering a 5-hour lockout right before you're about to close a PR. Debugging sessions are token-heavy, especially when Claude is iterating through error traces.

The built-in /usage command gives you a snapshot, but it requires you to remember to check. Usagebar solves this passively: it sits in your macOS menu bar and shows your real-time Claude Code usage with automatic alerts at 50%, 75%, and 90% capacity. You see your limit approaching without breaking focus to switch context.

Usagebar also shows you exactly when your usage window resets (as explained in the usage reset guide), so you can plan intensive debugging sprints around your available window rather than discovering the wall mid-session. Credentials are stored in the macOS Keychain, not in plain text config files. Pricing is pay-what-you-want, with a free option for students.

Get Usagebar and eliminate the context switch of manually checking usage while you're in the middle of a debugging loop.

Key takeaways

  1. Use PostToolUse hooks scoped to "Bash" to intercept command failures before Claude retries blindly.
  2. Return non-zero exit codes from hook scripts to signal hard blocks; use structured stdout messages so Claude has actionable context.
  3. Log errors to a file (.claude/error.log) and aggregate them in a Stop hook for a clean session summary.
  4. Scope separate hooks to the Task tool when running multi-agent workflows to catch subagent failures.
  5. Combine hooks with Claude Code's built-in error fixing capabilities for a full error management loop.
  6. Monitor token consumption passively with Usagebar to avoid hitting usage limits during long debug sessions.

Sources

Track Your Claude Code Usage

Never hit your usage limits unexpectedly. Usagebar lives in your menu bar and shows your 5-hour and weekly limits at a glance.

Get Usagebar