What Are Claude Code Hooks?
Hooks are shell commands that execute automatically when certain events occur in Claude Code. They’re configured in your settings.json file and can trigger on events like:
- PreToolUse - Before Claude uses any tool
- PostToolUse - After Claude uses any tool
- SessionStart - When a Claude Code session begins
- SessionEnd - When a Claude Code session ends
- Stop - When Claude stops generating a response
- Notification - When Claude needs to notify you
- UserPromptSubmit - When you submit a prompt
- PreCompact - Before context compaction occurs
Hook Configuration Structure
Hooks are defined in ~/.claude/settings.json (global) or .claude/settings.json (project-level):
{
  "hooks": {
    "EventName": [
      {
        "matcher": "optional-pattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-shell-command",
            "timeout": 5000
          }
        ]
      }
    ]
  }
}- matcher (optional): Pattern to match tool names or other criteria
- type: Currently only "command"is supported
- command: Shell command to execute
- timeout (optional): Timeout in milliseconds for the command
Example 1: Audio Notifications
One simple but effective use of hooks is to get audio feedback when events occur. This is especially useful when Claude finishes a long-running task.
Configuration
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Ping.aiff"
          }
        ]
      }
    ],
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Ping.aiff"
          }
        ]
      }
    ]
  }
}Or use text-to-speech:
{
  "type": "command",
  "command": "say 'Task completed'"
}What This Does
- Plays a sound when Claude stops generating a response
- Plays a sound when Claude sends a notification
- Helps you know when to check back if you’re multitasking
Example 2: Auto-Syncing MCP Server Configuration
A more advanced use case is automatically backing up your MCP (Model Context Protocol) server configuration to your dotfiles repository.
Problem
Claude Code stores global MCP server configurations in ~/.claude.json, but this file also contains:
- Session history for every project you’ve worked on
- Usage statistics and token counts
- UI state and preferences
- Cached feature flags
- etc.
I want to version control MCP servers without tracking all this ephemeral state.
Solution
Use a SessionEnd hook to automatically extract and sync just the MCP configuration.
Files in This Configuration
- .claude/mcp-servers.json- MCP server configurations (auto-synced)
- .claude/settings.json- Claude Code settings with hooks
Step 1: Create a Sync Script
Create ~/.dotfiles/scripts/sync-mcp-config.sh:
#!/bin/bash
# Sync MCP servers configuration from ~/.claude.json to dotfiles
SOURCE_FILE="$HOME/.claude.json"
DEST_FILE="$HOME/.dotfiles/claude/.claude/mcp-servers.json"
if [ ! -f "$SOURCE_FILE" ]; then
    echo "Error: $SOURCE_FILE not found"
    exit 1
fi
# Extract mcpServers section using jq
echo "Extracting mcpServers from $SOURCE_FILE..."
jq '{mcpServers: .mcpServers}' "$SOURCE_FILE" > "$DEST_FILE"
if [ $? -eq 0 ]; then
    echo "✓ MCP servers configuration synced successfully to $DEST_FILE"
else
    echo "✗ Failed to sync MCP configuration"
    exit 1
fiMake it executable:
chmod +x ~/.dotfiles/scripts/sync-mcp-config.shStep 2: Add a Hook
Add this to your ~/.claude/settings.json:
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/.dotfiles/scripts/sync-mcp-config.sh"
          }
        ]
      }
    ]
  }
}What This Does
- Automatic Backup: Every time you exit Claude Code, your MCP configuration is automatically extracted and saved
- Clean Tracking: Only the MCP servers are tracked, not the entire state file
- Portable Setup: Easy to restore your MCP setup on new machines
- No Manual Work: You never have to remember to sync the configuration
Result
Your claude/.claude/mcp-servers.json will look like:
{
  "mcpServers": {
    "chrome-devtools": {
      "type": "stdio",
      "command": "npx",
      "args": ["chrome-devtools-mcp@latest"],
      "env": {}
    },
    "context7": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp@latest"],
      "env": {}
    }
  }
}This file is clean, version-controlled, and automatically updated.
More Hook Ideas
Run Tests After Code Changes
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm test",
            "timeout": 30000
          }
        ]
      }
    ]
  }
}Format Code Automatically
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $FILE"
          }
        ]
      }
    ]
  }
}Log Session Activity
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): Session started\" >> ~/.claude-sessions.log"
          }
        ]
      }
    ],
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): Session ended\" >> ~/.claude-sessions.log"
          }
        ]
      }
    ]
  }
}Send Desktop Notifications
# macOS
"command": "osascript -e 'display notification \"Claude has finished\" with title \"Claude Code\"'"
# Linux
"command": "notify-send 'Claude Code' 'Task completed'"