Skip to content
LinkedInX

Hooks Implementation — Automate with Lifecycle Hooks

About 10 minutes

Target audience: Those who understand the basic settings of Settings JSON and want to add automation and control to Claude's behavior. It is recommended to read [Settings JSON Design](./settings) first.
Prerequisites: The basic structure of `.claude/settings.json`, basic JSON writing, and shell script fundamentals

By configuring hooks (Hooks), you can automatically execute any shell commands at the timing when Claude Code runs tools or returns responses. This page covers the 4 hook event types, environment variables, the meaning of exit codes, and how to configure them in .claude/settings.json — in order.


A hook is a mechanism that allows shell commands to run at specific timings when Claude Code calls tools or generates responses. Hooks are configured in the hooks key of .claude/settings.json.

The main uses of hooks are as follows.

  • Logging: Record which tools were called and when to a file
  • Validation: Block dangerous commands from being executed
  • Auto-formatting: Automatically run a linter after a file is saved
  • Notification: Notify via system notification when Claude completes its work

Claude Code has 4 hook event types.

EventTimingMain Use
PreToolUseBefore tool executionValidation, blocking
PostToolUseAfter tool executionLogging, notification, post-processing
UserPromptSubmitWhen the user inputsPrompt transformation, pre-processing
StopWhen the response endsCompletion notification, post-processing

PreToolUse is called before tool execution, so it functions as a gatekeeper that can block the use of specific tools. PostToolUse is called after tool execution, so it is suitable for adding side effects (log writing, notifications, etc.).


Hooks are configured in the hooks key of .claude/settings.json.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Tool: $CLAUDE_TOOL_NAME'"
          }
        ]
      }
    ]
  }
}

The structure of the configuration is as follows.

  • hooks: The root key for hook configuration
  • PreToolUse / PostToolUse / UserPromptSubmit / Stop: Event name
  • matcher: Which tool name to apply the hook to. Writing "Bash" means it only runs when the Bash tool is used. When omitted, it applies to all tools
  • type: The type of hook. Currently only "command" is supported
  • command: The shell command to execute

During hook execution, Claude Code provides the following environment variables.

VariableContentAvailable Events
$CLAUDE_TOOL_NAMEName of the called tool (e.g., Bash, Read)PreToolUse, PostToolUse
$CLAUDE_TOOL_INPUTInput to the tool (JSON format)PreToolUse, PostToolUse
$CLAUDE_TOOL_RESULTTool execution result (JSON format)PostToolUse only

Since $CLAUDE_TOOL_INPUT is a JSON string, you can use the jq command to extract specific fields.

# Example of extracting the command string passed to the Bash tool
echo "$CLAUDE_TOOL_INPUT" | jq -r '.command'

The behavior of Claude Code changes depending on the exit code of the hook script.

Exit codeMeaning
0Success. Continue tool execution
1Error. If PreToolUse, blocks tool execution
2Non-blocking error. Display an error message but continue execution

If a PreToolUse hook returns exit 1, Claude aborts that tool’s execution. Even if a PostToolUse hook returns exit 1, the tool operations already executed cannot be undone.


If hooks perform heavy processing (network communication, large file processing, etc.), Claude’s response slows down. Run heavy processing in the background.

# Example of running in the background (add & to background it)
some-heavy-command "$CLAUDE_TOOL_NAME" &

When a hook blocks, always output an error message before exit 1. Without a message, the user won’t know why it was blocked.

echo 'ERROR: npm run build requires explicit user approval.'
exit 1

If too many blocking hooks are set, Claude Code becomes hard to use. Narrow down the blocking conditions specifically to avoid accidentally blocking necessary operations.

Recording tool usage logs with a PostToolUse hook to a file is useful for auditing and debugging.

echo "$(date): $CLAUDE_TOOL_NAME" >> /tmp/claude-tool-log.txt

Rather than writing long commands directly in the command field, it is easier to test and maintain if you extract them as shell scripts in a separate file and call them.


Step 1: Check the Current Hook Configuration

Section titled “Step 1: Check the Current Hook Configuration”
cat .claude/settings.json

Check the presence of the hooks key and the current configuration contents.

✅ Verify: If the file contents are displayed, the read was successful. If the hooks key is absent, add it in the next step.

Step 2: Add a PreToolUse Hook to Prevent Accidentally Running npm run build

Section titled “Step 2: Add a PreToolUse Hook to Prevent Accidentally Running npm run build”

Add the following configuration to the hooks section of .claude/settings.json.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'npm run build'; then echo 'ERROR: npm run build requires explicit user approval.'; exit 1; fi"
          }
        ]
      }
    ]
  }
}

This hook blocks the Bash tool input and displays an error message if it contains npm run build.

✅ Verify: After saving .claude/settings.json, confirm with cat .claude/settings.json that the configuration has been correctly recorded.

In a Claude Code session, make the following request.

Please run npm run build

When Claude tries to run npm run build with the Bash tool, confirm that the hook returns exit 1 and blocks it.

✅ Verify: If Claude’s response contains something to the effect that “npm run build was blocked” or “explicit user approval is required,” the hook is working correctly.

Step 4: Add a Stop Hook to Notify macOS When Work Is Complete

Section titled “Step 4: Add a Stop Hook to Notify macOS When Work Is Complete”

Add a Stop hook that sends a macOS notification when Claude finishes a response.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'npm run build'; then echo 'ERROR: npm run build requires explicit user approval.'; exit 1; fi"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude has finished working\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

✅ Verify: If “Claude has finished working” appears in the macOS Notification Center when Claude returns the next response, it is a success. On non-macOS environments, replace osascript with the appropriate command.


  • Hooks are a mechanism to run shell commands at specific timings in the Claude Code lifecycle
  • The 4 event types are PreToolUse (before tool), PostToolUse (after tool), UserPromptSubmit (on input), and Stop (on end)
  • During hook execution, $CLAUDE_TOOL_NAME, $CLAUDE_TOOL_INPUT, and $CLAUDE_TOOL_RESULT are available
  • Returning exit 1 in PreToolUse can block tool execution
  • Configure hooks in the hooks key of .claude/settings.json, designing them to be lightweight, explicit, and testable


Q: How does Claude behave when blocked by a PreToolUse hook?

A: When the hook returns exit 1, Claude does not execute that tool and receives the error message written to stdout as context. After that, Claude tries a different approach or reports to the user based on the content of the error.

Q: If a PostToolUse hook returns exit 1, can the tool’s execution result be cancelled?

A: It cannot be cancelled. PostToolUse is called after tool execution, so operations that have already completed (such as file writes) cannot be undone. Perform pre-execution validation with PreToolUse.

Q: What happens if matcher is omitted?

A: The hook executes for all tool calls. Not just Bash, but also Read, Write, and Glob become targets, so if the hook performs heavy processing, the response may slow down significantly. If you want to target only specific tools, explicitly specify matcher.

Q: Is it necessary to restart Claude Code after changing the hook configuration?

A: Changes to .claude/settings.json are read across sessions. To reflect changes in the current session, restart Claude Code or start a new session.

Q: What command can be used instead of osascript on Windows?

A: On Windows, you can use powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Claude has finished working')". On Linux, notify-send "Claude has finished working" works (requires libnotify).

See the references for the external specifications and background sources used on this page.[1][2]

  1. Anthropic, Claude Code documentation
  2. Anthropic, Claude API documentation
Quiz