Claude Code hooks: intercept every tool call before it runs
<h1> Claude Code hooks: intercept every tool call before it runs </h1> <p>The Claude Code source leak revealed something most developers haven't discovered yet: a full hooks system that lets you intercept, log, or block any tool call Claude makes — before it executes.</p> <p>This isn't documented anywhere officially. Here's how it works.</p> <h2> What are Claude Code hooks? </h2> <p>Hooks are shell commands that run at specific points in Claude Code's execution cycle:</p> <ul> <li> <strong>PreToolUse</strong> — runs before Claude calls any tool (Bash, Read, Write, etc.)</li> <li> <strong>PostToolUse</strong> — runs after a tool completes</li> <li> <strong>Notification</strong> — runs when Claude sends you a notification</li> <li> <strong>Stop</strong> — runs when a session ends</li> </ul>
Claude Code hooks: intercept every tool call before it runs
The Claude Code source leak revealed something most developers haven't discovered yet: a full hooks system that lets you intercept, log, or block any tool call Claude makes — before it executes.
This isn't documented anywhere officially. Here's how it works.
What are Claude Code hooks?
Hooks are shell commands that run at specific points in Claude Code's execution cycle:
-
PreToolUse — runs before Claude calls any tool (Bash, Read, Write, etc.)
-
PostToolUse — runs after a tool completes
-
Notification — runs when Claude sends you a notification
-
Stop — runs when a session ends
You define them in your .claude/settings.json.
The config format
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo '[HOOK] Claude about to run Bash' >> /tmp/claude-audit.log" } ] } ], "PostToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "echo '[HOOK] Claude wrote a file' >> /tmp/claude-audit.log" } ] } ] } }{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo '[HOOK] Claude about to run Bash' >> /tmp/claude-audit.log" } ] } ], "PostToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "echo '[HOOK] Claude wrote a file' >> /tmp/claude-audit.log" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Every Bash call gets logged. Every file write gets logged. Full audit trail, zero effort.
Practical hook: block dangerous commands
Want to prevent Claude from ever running git reset --hard or rm -rf?
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo $CLAUDE_TOOL_INPUT | grep -E 'git reset --hard|rm -rf|drop table|DROP TABLE' && echo 'BLOCKED' && exit 1 || exit 0" } ] } ] } }{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo $CLAUDE_TOOL_INPUT | grep -E 'git reset --hard|rm -rf|drop table|DROP TABLE' && echo 'BLOCKED' && exit 1 || exit 0" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
If the command matches, the hook exits 1 — Claude Code sees the tool as blocked and doesn't proceed.
Practical hook: auto-backup before writes
{ "hooks": { "PreToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "FILE=$(echo $CLAUDE_TOOL_INPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); [ -f \"$FILE\" ] && cp \"$FILE\" \"$FILE.$(date +%s).bak\" && echo \"Backed up $FILE\"" } ] } ] } }{ "hooks": { "PreToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "FILE=$(echo $CLAUDE_TOOL_INPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); [ -f \"$FILE\" ] && cp \"$FILE\" \"$FILE.$(date +%s).bak\" && echo \"Backed up $FILE\"" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Before Claude overwrites any file, it gets backed up automatically. No more lost work.
Practical hook: notify on session end
{ "hooks": { "Stop": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code session complete\" with title \"Claude Code\"' 2>/dev/null || notify-send 'Claude Code' 'Session complete' 2>/dev/null" } ] } ] } }{ "hooks": { "Stop": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code session complete\" with title \"Claude Code\"' 2>/dev/null || notify-send 'Claude Code' 'Session complete' 2>/dev/null" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Mac or Linux desktop notification when Claude finishes a long task. Useful for background runs.
The CLAUDE_TOOL_INPUT environment variable
Hooks receive tool call data via the CLAUDE_TOOL_INPUT environment variable as JSON. The schema depends on the tool:
-
Bash: {"command": "ls -la", "description": "List files"}
-
Write: {"file_path": "/path/to/file", "content": "..."}
-
Read: {"file_path": "/path/to/file"}
This means you can build arbitrarily sophisticated interceptors in any language.
Python interceptor example
Create /usr/local/bin/claude-bash-hook:
#!/usr/bin/env python3 import os import sys import json import subprocess from datetime import datetime#!/usr/bin/env python3 import os import sys import json import subprocess from datetime import datetimetool_input_str = os.environ.get('CLAUDE_TOOL_INPUT', '{}') try: tool_input = json.loads(tool_input_str) except: tool_input = {}
command = tool_input.get('command', '')
Blocklist
DANGEROUS = [ 'git reset --hard', 'rm -rf /', 'DROP TABLE', 'truncate', 'format c:' ]
for danger in DANGEROUS: if danger.lower() in command.lower(): print(f'BLOCKED: {danger}', file=sys.stderr) sys.exit(1)
Audit log
with open('/tmp/claude-commands.log', 'a') as f: f.write(f"{datetime.now().isoformat()} | {command[:200]}\n")
sys.exit(0)`
Enter fullscreen mode
Exit fullscreen mode
Then in .claude/settings.json:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "claude-bash-hook" } ] } ] } }{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "claude-bash-hook" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Now every bash command Claude runs goes through your Python script first.
The bigger picture
The hooks system is part of what makes Claude Code surprisingly powerful once you get past the defaults. Combined with a solid CLAUDE.md and settings.json, you essentially get:
-
CLAUDE.md → what Claude should do (instructions)
-
settings.json permissions → what Claude is allowed to do (capabilities)
-
hooks → what happens around what Claude does (observability + guardrails)
Three layers. Most developers only use the first one.
If you're using Claude via API directly (not Claude Code), the flat-rate proxy at simplylouie.com gives you full API access for $2/month — same model, no per-token billing anxiety. Useful for testing hooks without burning credits.
DEV Community
https://dev.to/subprime2010/claude-code-hooks-intercept-every-tool-call-before-it-runs-1ik7Sign in to highlight and annotate this article

Conversation starters
Daily AI Digest
Get the top 5 AI stories delivered to your inbox every morning.
More about
claudemodelclaude code
30 Days of Building a Small Language Model — Day 1: Neural Networks
Welcome to day one. Before I introduce tokenizers, transformers, or training loops, we start where almost all modern machine learning starts: the neural network. Think of the first day as laying down the foundation you will reuse for the next twenty-nine days. If you have ever felt that neural networks sound like a black box, this post is for you. We will use a simple picture is this a dog or a cat? and walk through what actually happens inside the model, in plain language. What is a neural network? A neural network is made of layers. Each layer has many small units. Data flows in one direction: each unit takes numbers from the previous layer, updates them, and sends new numbers forward. During training, the network adjusts itself so its outputs get closer to the correct answers on example
Knowledge Map
Connected Articles — Knowledge Graph
This article is connected to other articles through shared AI topics and tags.
More in Models

30 Days of Building a Small Language Model — Day 1: Neural Networks
Welcome to day one. Before I introduce tokenizers, transformers, or training loops, we start where almost all modern machine learning starts: the neural network. Think of the first day as laying down the foundation you will reuse for the next twenty-nine days. If you have ever felt that neural networks sound like a black box, this post is for you. We will use a simple picture is this a dog or a cat? and walk through what actually happens inside the model, in plain language. What is a neural network? A neural network is made of layers. Each layer has many small units. Data flows in one direction: each unit takes numbers from the previous layer, updates them, and sends new numbers forward. During training, the network adjusts itself so its outputs get closer to the correct answers on example





Discussion
Sign in to join the discussion
No comments yet — be the first to share your thoughts!