Skip to content

How to Use Hooks in Claude Code for Custom Agent Behavior

I wanted Claude Code to automatically load my project context when I started a session and log every bash command it ran. I didn’t want to manually configure these behaviors every time. That’s when I discovered hooks—Claude Code’s built-in system for running deterministic logic at specific points in the agent lifecycle.

What Are Hooks

Hooks are functions that run at predefined moments during Claude Code’s execution. They let you inject custom logic without modifying Claude Code itself. You can load context dynamically, validate inputs before tools run, or send notifications after operations complete.

Think of hooks as event listeners. When a specific event occurs in Claude Code’s lifecycle, your hook function fires and can inspect, modify, or react to that event.

Hook Lifecycle Stages

Claude Code supports four hook types, each firing at a different point in the agent lifecycle.

SessionStart

The SessionStart hook runs when you begin a new Claude Code session. I use this hook to automatically load project-specific context into the conversation. For example, when I start working on my blog, I want Claude to know my writing style guidelines and the current state of my content pipeline.

settings.json
{
"hooks": {
"SessionStart": [
{
"command": "cat ~/.claude/context/my-project-context.md"
}
]
}
}

This hook loads context from a markdown file I maintain with project details, coding standards, and current priorities.

PreToolUse

The PreToolUse hook fires before Claude executes any tool. I use this to validate inputs, log commands, or even block certain operations. The hook receives the tool name and parameters, letting me inspect what’s about to happen.

settings.json
{
"hooks": {
"PreToolUse": [
{
"command": "echo '[PRE] Tool: $CLAUDE_TOOL_NAME' >> ~/.claude/logs/tool-usage.log"
}
]
}
}

This configuration logs every tool invocation before it runs. I’ve used this to debug unexpected behavior and understand Claude’s decision-making patterns.

PostToolUse

The PostToolUse hook runs after a tool completes execution. I find this useful for cleanup, formatting, or triggering external systems. The hook receives the tool name, parameters, and the result.

settings.json
{
"hooks": {
"PostToolUse": [
{
"command": "npx prettier --write $CLAUDE_EDITED_FILE",
"matcher": "Edit"
}
]
}
}

This hook automatically formats any file I edit using Prettier. The matcher ensures this only runs for Edit tool invocations.

Stop

The Stop hook fires when a Claude Code session ends. I use this for final validation, audit logging, or cleanup tasks. It’s the last chance to run logic before the session closes.

settings.json
{
"hooks": {
"Stop": [
{
"command": "node ~/.claude/scripts/audit-console-logs.js"
}
]
}
}

This hook runs a script that checks all modified files for console.log statements before the session ends, reminding me to clean up debugging code.

Configuration Example

Here’s a complete settings.json with multiple hooks configured for different purposes.

settings.json
{
"hooks": {
"SessionStart": [
{
"command": "cat ~/.claude/context/project-context.md"
}
],
"PreToolUse": [
{
"command": "echo '[PRE] $CLAUDE_TOOL_NAME at $(date)' >> ~/.claude/logs/tool.log"
}
],
"PostToolUse": [
{
"command": "npx prettier --write $CLAUDE_EDITED_FILE",
"matcher": "Edit"
},
{
"command": "npx tsc --noEmit",
"matcher": "Edit",
"glob": "*.ts"
}
],
"Stop": [
{
"command": "node ~/.claude/scripts/session-audit.js"
}
]
}
}

This configuration loads context at session start, logs tool usage, auto-formats edited files, checks TypeScript types after edits, and audits the session before it ends.

Practical Use Cases

I’ve found hooks useful for several real-world scenarios.

Automated Code Formatting: I configured a PostToolUse hook that runs Prettier on any file after I edit it. This ensures consistent formatting without me remembering to run the formatter manually.

Type Safety: I added a hook that runs TypeScript type checking after editing .ts files. If I introduce a type error, I see it immediately rather than discovering it later during build.

Security Audit: I use a PreToolUse hook that checks for API keys or secrets in bash commands before they execute. If Claude tries to run a command with a hardcoded secret, the hook blocks it.

Context Loading: I maintain a project context file with coding standards, current priorities, and domain knowledge. A SessionStart hook loads this automatically, so Claude always has relevant context.

Audit Trail: I log every tool invocation with timestamps. This helps me understand Claude’s behavior patterns and debug issues when something unexpected happens.

Common Mistakes

I made several mistakes when I started using hooks.

Running Expensive Operations in SessionStart: I initially loaded my entire codebase into context at session start. This made sessions slow and consumed tokens unnecessarily. Now I load only essential context.

Not Using Matchers: I configured a PostToolUse hook to run TypeScript checks, but it ran after every tool, not just file edits. Adding a matcher fixed this and saved time.

Blocking Critical Operations: I created a PreToolUse hook that required approval for git push operations. This was annoying when I explicitly requested pushes. I learned to trust Claude’s judgment for intentional operations.

Ignoring Hook Output: Hooks can provide feedback to Claude, but I initially ignored this. Now I use hooks to warn Claude about issues, like console.log statements or missing tests.

Over-Engineering: I tried to build complex automation with hooks before understanding my actual needs. Starting with simple logging hooks helped me identify what automation would actually help.

Summary

Hooks let you inject custom logic into Claude Code’s lifecycle at four key moments: SessionStart, PreToolUse, PostToolUse, and Stop. I use them to load context automatically, log tool usage, format code, check types, and audit sessions.

The key is starting simple. Add a logging hook first to understand Claude’s behavior, then gradually add automation that saves you time. Hooks work best when they handle repetitive tasks you’d otherwise do manually.

Configure hooks in your settings.json file under the “hooks” key. Each hook type takes an array of commands, and you can use matchers and globs to target specific tools or file types.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments