← cd ../blog
Deep Dive2026-02-2010 min read

Claude Code Hooks: A Complete Guide to Automating Your Workflow

Everything you need to know about Claude Code hooks — what they are, how they work, and how to use them for notifications, logging, and custom automations.

Claude Code hooks are one of the most underused features in the CLI. They let you run custom commands whenever Claude Code does something — starts a session, asks for permission, finishes a task, or uses a tool.

Think of them as webhooks for your terminal. Every time Claude Code hits a lifecycle event, your hook fires. That opens up notifications, logging, guardrails, custom automations — anything you can do with a shell command.

What Are Hooks?

Hooks are shell commands that Claude Code runs at specific points in its lifecycle. You define them in your ~/.claude/settings.json file.

When an event fires, Claude Code executes your hook command synchronously. The hook receives context about what triggered it — session ID, working directory, tool name, and more — either as environment variables or via JSON on stdin.

Available Hook Events

Claude Code supports these hook events:

SessionStart

Fires when a new Claude Code session begins. Use it to initialize logging, send a "session started" notification, or set environment variables for the session.

UserPromptSubmit

Fires when you send a message to Claude. Useful for logging prompts or triggering external workflows based on what you're asking Claude to do.

PreToolUse

Fires before Claude executes a tool (Bash, Edit, Read, etc.). This is powerful for guardrails — you can inspect what Claude is about to do and block it by returning a non-zero exit code. For example, you could prevent rm -rf / from ever running.

Stop

Fires when Claude stops and is waiting for your input. This is the most common hook for notifications — it tells you "Claude is done and needs you."

Notification

Fires when Claude explicitly sends a notification to you. This is separate from Stop — it's for custom messages Claude wants you to see, like "Build failed" or "All tests passing."

PermissionRequest

Fires when Claude needs your permission to do something — run a command, edit a file, make an API call. The hook receives the tool name and details about what Claude wants to do.

SessionEnd

Fires when a session ends. Use it for cleanup, final logging, or sending a "session ended" notification.

How to Configure Hooks

Hooks live in ~/.claude/settings.json. Here's the basic structure:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}

Each hook event takes an array of hook groups. Each group has:

  • matcher — a pattern to filter when this hook runs. An empty string means "always run." For PreToolUse, you can match on tool names like Bash or Edit.
  • hooks — an array of commands to run when the event fires.
  • type — currently only "command" is supported.
  • command — the shell command to execute.

Environment Variables in Hooks

Claude Code exposes context as environment variables that your hook commands can use:

VariableDescription
$SESSION_IDUnique identifier for the current session
$CWDCurrent working directory
$CLAUDE_SESSION_IDSame as $SESSION_ID (v2.1.9+)
$CLAUDE_WORKING_DIRSame as $CWD (v2.1.9+)
$CLAUDE_TOOL_NAMEName of the tool being used (in tool hooks)

Practical Examples

1. Desktop Notification When Claude Stops

The simplest useful hook — a macOS notification when Claude is done:

{
  "hooks": {
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "osascript -e 'display notification \"Claude needs you\" with title \"Claude Code\"'"
      }]
    }]
  }
}

2. Push Notifications to Your iPhone

Send a webhook to a notification service. Agentfy uses this pattern — you point your hooks at its webhook URL and get push notifications, Live Activities, and Dynamic Island updates on your iPhone:

{
  "hooks": {
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "curl -s -X POST https://your-webhook-url -H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' -d '{"hook_event_name": "Stop", "session_id": "$SESSION_ID", "cwd": "$CWD"}'"
      }]
    }],
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "curl -s -X POST https://your-webhook-url -H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' -d '{"hook_event_name": "SessionStart", "session_id": "$SESSION_ID", "cwd": "$CWD"}'"
      }]
    }]
  }
}

3. Block Dangerous Commands

Use PreToolUse to prevent Claude from running specific commands. If your hook exits with code 2, the tool use is blocked:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo $CLAUDE_TOOL_INPUT | grep -q 'rm -rf /' && exit 2 || exit 0"
      }]
    }]
  }
}

4. Log All Sessions

Append session events to a log file for tracking your Claude Code usage:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "echo \"$(date): Session $SESSION_ID started in $CWD\" >> ~/.claude/session.log"
      }]
    }],
    "SessionEnd": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "echo \"$(date): Session $SESSION_ID ended\" >> ~/.claude/session.log"
      }]
    }]
  }
}

Multiple Hooks Per Event

You can chain multiple hooks on the same event. They run in order. For example, you might log to a file and send a push notification on every Stop event. Each hook in the array executes independently — if one fails, the others still run.

Project-Level vs Global Hooks

Hooks in ~/.claude/settings.json apply to all your Claude Code sessions. You can also add project-specific hooks in a .claude/settings.json file at the root of your project.

Project-level hooks run in addition to global hooks, not instead of them. This is useful for project-specific guardrails — for example, blocking database drops in production projects.

Tips and Gotchas

  • Hooks run synchronously. A slow hook blocks Claude Code. Keep them fast — use curl -s (silent mode) and avoid commands that take more than a second or two.
  • After editing settings.json, restart Claude Code. Use /resume to continue your conversation without losing context.
  • Test hooks manually first. Copy the command and run it in your terminal to make sure it works before adding it to settings.json.
  • Use -s with curl. Without it, curl outputs progress bars that can clutter Claude Code's output.
  • Session IDs change on resume. If you use --resume, the new session gets a new ID. Don't rely on session IDs being stable across resumes.

What's Next for Hooks

Hooks are still evolving. The current version supports shell commands, but the Claude Code team is actively expanding what's possible. If you want to stay on top of new hook events and capabilities, check the official hooks documentation.

In the meantime, even the basic hooks above can save you hours of terminal babysitting. Set up notifications, add some guardrails, and let Claude Code work while you don't.

Ready to stop babysitting your terminal?

Download Agentfy