The Complete Guide to Claude Code Hooks | Implementation Techniques for Accelerating Development with Tool Event Automation (2026 Edition)
Hello, this is Hamamoto from TIMEWELL.
For engineers who have been using Claude Code for more than six months, the feature that has rapidly gained presence since entering 2026 is "Hooks." What happens if the AI agent suddenly runs rm -rf? What if it forgets to format after an Edit and commits messy code? Hooks provide a definitive answer to these nagging, everyday fears.
What's interesting is that despite being unassuming, Hooks fundamentally change how teams operate. Even if you write "always run prettier" in CLAUDE.md, the AI will forget some days. Since request-based instructions fail, you lay down rails with Hooks and enforce them. This is the "deterministic control" philosophy that Anthropic itself emphasizes[^1].
In this article, based on the official specifications and real-world usage as of April 2026, I'll summarize the full picture of Claude Code Hooks along with implementation techniques. From the behavior of major events like PreToolUse and PostToolUse, to writing settings.json, security design, and CI/CD integration, I aim to cover everything you need in a single read.
The True Nature of Hooks and Three Cadences
Claude Code Hooks is a feature that hooks shell commands or HTTP requests you write into events that occur during the Claude Code session lifecycle. The configuration target is the hooks key in settings.json. Just by declaring matchers and commands, you can intervene in the AI's behavior[^1].
The official documentation organizes events into three cadences[^1]:
- Session-level (SessionStart, SessionEnd)
- Turn-level (UserPromptSubmit, Stop, StopFailure)
- Tool-call-level (PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch)
On top of these, there are Notification for alerts, PermissionRequest for permission dialogs, and SubagentStop for when subagents finish. For example, SessionStart fires on launch, resume, /clear, and /compact, and you can distinguish the reason using the source field. UserPromptSubmit runs the moment you hit Enter, before Claude processes the prompt, making it ideal for censoring prompt content or injecting external context.
What makes this powerful is that the shape of JSON passed for each event is fixed. PreToolUse includes tool_name, tool_input, and tool_use_id, while PostToolUse also adds tool_response. Hook scripts receive this JSON through standard input, parse it with tools like jq, and make decisions. To return values, they either output hookSpecificOutput to standard output or forcibly block with exit code 2. This two-layer structure is the implementation convention of Claude Code Hooks[^2].
What confuses first-time users is that Hooks is not a "request to the AI" but "a program running on the host OS." It doesn't run on Anthropic's servers. It runs on your MacBook or Linux shell, with your permissions. That's what enables deterministic control, but mistakes can also shoot you in the foot. Understanding this tension upfront prevents later design decisions from drifting.
Interested in leveraging AI?
Download our service materials. Feel free to reach out for a consultation.
settings.json Basic Structure and Placement Rules
There are three tiers of configuration files by purpose. First, ~/.claude/settings.json applies to all users, then the project-shared .claude/settings.json for team sharing, and finally .claude/settings.local.json for personal experiments you don't commit. For plugin bundling, there's hooks/hooks.json, and you can also write hooks in the frontmatter of skills or agents[^1].
The one you'll use most in practice is the shared .claude/settings.json. Commit it to Git and distribute it, and the whole team operates under the same rules. Promises that get skipped in CLAUDE.md are enforced whether anyone likes it or not when set as Hooks.
The basic form looks like this:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-bash.sh",
"timeout": 30
}
]
}
]
}
}
matcher is a string that matches tool names. You can use exact matches like "Bash", pipe-joined values like "Edit|Write", or regular expressions. To catch tools coming through an MCP server, patterns like "mcp__memory__.*" let you hook into all tools under the memory server. Using "*" or omitting it hits all tools[^1].
In addition to command, type offers four more options: http for POSTing to external endpoints, prompt for LLM-based judgment, agent for validation by subagents, and mcp_tool for calling MCP tools[^1]. Beginners should start with command, and only consider http once you're ready to roll out to the whole team. That's a sensible progression.
Some useful environment variables are provided too. $CLAUDE_PROJECT_DIR gives the absolute path of the project root, and plugins can access ${CLAUDE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_DATA}. Typing /hooks shows the list of currently active hooks in your session, which is the fastest way to debug. To emergency-stop all hooks, adding "disableAllHooks": true kills them instantly.
The First Implementation to Build with PreToolUse and PostToolUse
The first things you should build in practice are PreToolUse to block dangerous commands, and PostToolUse to handle automatic formatting after edits. Both pay off in five minutes, making them ideal practice for getting used to Hooks.
First, let's look at an example of blocking rm -rf and similar commands with PreToolUse. Write this in .claude/hooks/guard-bash.sh:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -qE '(rm\s+-rf|sudo\s+rm|mkfs|dd\s+if=)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Blocked because it is a destructive command"
}
}'
exit 0
fi
exit 0
The point is to keep exit code at 0 and return permissionDecision: "deny" through JSON. Exit code 2 also blocks, but returning a structured reason enables smarter recovery on Claude's side. decision has four options: allow, deny, ask, and defer. ask queries the user each time, and defer hands off to the next PermissionRequest stage[^1][^3].
Next is PostToolUse. Let me show a pattern that runs prettier and eslint immediately after an Edit, and sends failures back to Claude.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-after-edit.sh",
"timeout": 60
}
]
}
]
}
}
On the script side, it receives tool_input.file_path and runs npx prettier --write followed by npx eslint --fix based on the file extension. If eslint throws an error, output the reason to stderr and return exit code 2. Claude then reads the feedback, fixes the relevant spots, and re-edits. You no longer need to manually point out "the lint is failing" — the loop becomes automated. This is a well-known pattern repeatedly introduced in the official blog and DataCamp tutorials[^4].
Personally, I recommend starting with PostToolUse loose (don't fail with exit code 2, just log), then tightening once the rules stabilize. Going strict from day one causes Claude to retry endlessly and burn through turns, so calibrate to the character of your project.
Controlling Context with SessionStart, UserPromptSubmit, and Stop
If PreToolUse and PostToolUse are "safety devices at the tool level," SessionStart, UserPromptSubmit, and Stop are hooks for "controlling turns and the conversation itself." Once you work these, Claude's intelligence enters a new tier.
SessionStart runs exactly once right after launch, making it well-suited for injecting project state into Claude's context. For example, passing the results of git status and git log -10 --oneline as additional context makes Claude propose ideas assuming the latest work state from the moment of resume. On the JSON side, you return a string in the additionalContext field[^1].
UserPromptSubmit runs before the prompt is processed, so it's useful for both censoring and information addition. A common community implementation: "If the prompt contains an ID like TICKET-123, pull the ticket body from Linear and inject it into additional context"[^5]. Conversely, if a confidential filename is included, return decision: "block" to reject the entire prompt — a fitting censorship use case.
Stop runs when a turn completes. What I personally like is setting audio notifications on Stop. When running long tasks and looking away from the terminal, just a "ping" makes a huge productivity difference. Anthropic's official blog also introduces an example of calling say "Claude is done" (macOS text-to-speech) on Stop[^2].
SubagentStop is a dedicated event for when subagents finish. When you try to apply a Stop hook to a subagent, it automatically converts to SubagentStop[^3]. Use it when you want to notify differently from the parent agent's Stop, or separate logs.
The next snippet is a configuration example that sets up SessionStart, UserPromptSubmit, and Stop together.
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/inject-git-status.sh"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/expand-ticket-id.sh"
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "say 'Claude is done'"
}
]
}
]
}
}
Combining these three establishes a natural flow across the session: "thicken the context, reinforce the prompt, announce completion." Locking this in through config files reduces slip-ups compared to writing in CLAUDE.md.
Security and CI/CD Integration for Production Operations
One thing you must never forget with Hooks is that they run with the host OS's permissions. A third-party hook dropped into ~/.claude/settings.json can read your ~/.aws/credentials or ~/.ssh/id_rsa. The official documentation explicitly states, "Hook code can exfiltrate data or damage your system," and repeatedly warns to always review distributed hooks before installing them[^1].
As practical operational rules, I consistently follow three principles: start with minimum permissions and loosen when needed; when bringing in hooks from outside, read the full Git diff and never run anything with unclear lines; and explicitly deny commands accessing secret files in PreToolUse. The third in particular is easy to overlook, so here's a sample that blocks Bash commands attempting to read .env files or ~/.aws/:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
file=$(echo "$input" | jq -r '.tool_input.file_path // ""')
if echo "$cmd $file" | grep -qE '(\.env|\.aws/|id_rsa|credentials\.json)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Blocked access to a secret file"
}
}'
exit 0
fi
exit 0
With this much in place, you can confidently delegate even in "auto-accept" mode, which grants Claude Code stronger autonomy. Repositories like kornysietsma/claude-code-permissions-hook and karanb192/claude-code-hooks on GitHub serve as valuable templates for fine-grained permission control[^6].
When you step into CI/CD integration, type: "http" becomes the trump card. The classic setup triggers GitHub Actions' Repository Dispatch endpoint from PostToolUse or Stop. Claude Lab's explanation article details implementation patterns for bundling automatic PR review on creation, per-commit security scans, and regression tests triggered after staging deployment — all in one pipeline[^7].
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "http",
"url": "https://api.github.com/repos/acme/app/dispatches",
"headers": {
"Authorization": "Bearer $GITHUB_TOKEN",
"Accept": "application/vnd.github+json"
},
"body": {
"event_type": "claude-turn-complete",
"client_payload": { "session": "$SESSION_ID" }
}
}
]
}
]
}
}
Stop fires a GitHub Actions workflow every time, and from there CI runs lint, test, security scans, and Slack reporting. This keeps Claude Code from being just a coding assistant and opens the door to embedding it in autonomous engineering workflows.
When bringing this mechanism into enterprise engineering organizations, it tends to stick better when paired with governance and knowledge curation rather than running on Anthropic's API alone. TIMEWELL offers ZEROCK, an enterprise AI platform that keeps Japanese-centric knowledge confined to the AWS Tokyo region, and we've seen an uptick in implementation consultations on how to align internal documents with Claude Code. AI strategy design — such as how far to open up Claude Code organizationally, or which hooks to standardize — is the territory that our AI consulting service WARP accompanies. Designing together tends to be more efficient than building from scratch alone.
Summary and Next Steps
When you start using Claude Code Hooks, your initial impression is probably just "a convenient auto-formatting tool." But once you block danger with PreToolUse, guarantee quality with PostToolUse, thicken context with UserPromptSubmit, and fire events externally with Stop, AI coding transforms from "request-based training wheels" to a "rule-driven engineering foundation."
If you start today, I recommend this order:
- Create
.claude/settings.jsonand just write prettier execution in PostToolUse - Use it for a day and learn the pitfalls
- Add PreToolUse blocking for
rm -rfand secret files - Add
git statuscontext injection via SessionStart - Once comfortable, connect Stop to GitHub Actions or Slack for CI
As a related note, blending in Claude Code's Skills and Plugins amplifies the impact further. For Skill-side details, see 45 Recommended Claude Code Skills. For the full picture of plugin-based extensions, see A Deep Dive into the Superpowers Plugin. For multi-agent architecture design, see A Guide to Building Claude Code Agent Teams. Reading these together with Hooks should give you a clean overall picture of Claude Code.
Hooks may not be flashy, but they become the "backbone of design" for welcoming AI into your development environment. Ten lines of settings.json tweaking can significantly transform your development experience six months from now. This is the kind of thing you can't appreciate without trying — so start with just one thing: the PostToolUse formatter.
References
[^1]: Anthropic official documentation, "Hooks reference" https://code.claude.com/docs/en/hooks [^2]: Anthropic Blog, "Claude Code power user customization: How to configure hooks" https://claude.com/blog/how-to-configure-hooks [^3]: Anthropic official, "Automate workflows with hooks" https://code.claude.com/docs/en/hooks-guide [^4]: DataCamp Tutorial, "Claude Code Hooks: A Practical Guide to Workflow Automation" https://www.datacamp.com/tutorial/claude-code-hooks [^5]: GitHub disler/claude-code-hooks-mastery https://github.com/disler/claude-code-hooks-mastery [^6]: GitHub kornysietsma/claude-code-permissions-hook https://github.com/kornysietsma/claude-code-permissions-hook [^7]: Claude Lab, "Claude Code HTTP Hooks x GitHub Actions Integration Guide" https://claudelab.net/en/articles/claude-code/claude-code-http-hooks-cicd-github-actions-guide
