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 likeBashorEdit. - 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:
| Variable | Description |
|---|---|
| $SESSION_ID | Unique identifier for the current session |
| $CWD | Current working directory |
| $CLAUDE_SESSION_ID | Same as $SESSION_ID (v2.1.9+) |
| $CLAUDE_WORKING_DIR | Same as $CWD (v2.1.9+) |
| $CLAUDE_TOOL_NAME | Name 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/resumeto 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
-swith 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