7 Claude Code Hooks Most Developers Overlook (Especially Beginners)
So, you want to master Claude Code hooks? stop micromanaging Claude Code, there’s a better way to automate workflows.
7 Claude Code Hooks Most Developers Overlook (Especially Beginners)

So, you want to master Claude Code hooks? This should be your first step towards automation.
Today, I woke up to an email from a developer who just started using Claude Code.
“I keep hearing about hooks, but honestly, I’m scared to try them. What if I break something? I don’t even know where to start.”
It reminded me how far we’ve come, and what we likely ignored. Not everyone understands Claude Code hooks. And those few who do understand them are often afraid to try them.
If you are not a Premium Medium member, read the full article here for FREE, but please consider joining Medium to support my work — Thank you!
You don’t have to be a pro to start using Claude Code hooks to automate or improve your workflow.
In fact, hooks are the best way for beginners to learn automation faster and get excited about the possibilities.
If you haven’t started using Claude Code hooks in your workflow, you’re leaving a lot of power on the table.
I figured out the best way to help you get started is to share 7 simple hooks that teach you the core concepts and help you experiment with your own workflow.
But first, let’s cover the basics.
What Are Claude Code Hooks?
A Claude Code hook is a simple instruction you give Claude Code:
“Every time X happens, automatically do Y.”
They are custom commands that run automatically at specific points during Claude Code’s lifecycle. Every time Claude writes a file, runs a command, or finishes a task, a hook can step in and do something.
The difference between hooks and putting instructions in your CLAUDE.md is :
CLAUDE.mdis a suggestion, and Claude can choose to ignore it.- A hook is a guarantee, and it runs every single time, without exception.
So hooks give you deterministic control.
Where Do Hooks Live?

All your hooks go inside your .claude/settings.json file. The structure looks like this:
{
"hooks": {
"EventName": [
{
"matcher": "ToolName",
"hooks": [
{
"type": "command",
"command": "your-script-here"
}
]
}
]
}
}
Three things you need to understand:
- Events — the lifecycle moment (e.g.,
PreToolUse,PostToolUse,Stop) - Matchers — which tool triggers the hook (e.g.,
Bash,Write,Edit) - Handlers — what actually runs (a shell command, HTTP call, or prompt)
For this article, we’ll stick with command hooks since they cover most use cases and are the easiest to set up.
Now, let’s get into the 7 hooks you should try.
1. Auto-Format Every File Claude Writes
This is the “hello world” of Claude Code hooks.
Claude writes or edits a file, and the formatting is off, and you end up running Prettier manually after every change.
With a PostToolUse hook, Prettier runs automatically every time Claude writes or edits a file.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Here’s what’s happening:
PostToolUse— the hook fires after Claude uses a toolWrite|Edit— it only triggers when Claude writes or edits a file- The command runs Prettier on that exact file using the
$CLAUDE_TOOL_INPUT_FILE_PATHenvironment variable
If you’re working with Python, swap Prettier for Black:
{
"type": "command",
"command": "black \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
Note: if your formatter changes the file, Claude gets a system notification about the change. Over long sessions, this takes up your context window. A workaround is to format on commit using a
Stophook instead, but for getting started, this works great.
2. Get Notified When Claude Finishes a Task
This one is a quality-of-life hook that saves your time.
When you kick off a long task, switch to another window, and keep checking back every 30 seconds, wondering if Claude is done.
A Notification hook sends you a desktop notification when Claude needs your attention.
On macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
On Linux:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
The empty matcher
""means this hook fires on every notification event. That includes permission prompts, idle prompts, and auth dialogs.
3. Block Dangerous Shell Commands Before They Run
Claude Code can occasionally run commands you didn’t expect. Especially if you’re using --dangerously-skip-permissions to move faster, there's nothing stopping a rm -rf / or a curl | sh from executing.
A
PreToolUsehook can inspect every shell command before it runs and block the dangerous ones.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'CMD=$(jq -r .tool_input.command); for p in \"rm -rf /\" \"rm -rf ~\" \":(){ :|:& };:\" \"curl|sh\" \"wget|sh\"; do if echo \"$CMD\" | grep -qi \"$p\"; then echo \"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PreToolUse\\\",\\\"permissionDecision\\\":\\\"deny\\\",\\\"permissionDecisionReason\\\":\\\"Blocked: dangerous command detected\\\"}}\" && exit 0; fi; done'"
}
]
}
]
}
}
Here’s what this does:
- It fires on every
Bashtool call, before execution - It reads the command from the JSON input using
jq - It checks against a list of dangerous patterns —
rm -rf /,rm -rf ~, fork bombs, and piped installs - If a match is found, it returns a
denydecision with a reason
Claude sees the denial, gets the reason, and adjusts its approach. The command never executes.
For a cleaner setup, you can move the logic into its own script file:
#!/bin/bash
# .claude/hooks/block-dangerous.sh
CMD=$(jq -r '.tool_input.command')
BLOCKED_PATTERNS=("rm -rf /" "rm -rf ~" ":(){ :|:& };:" "curl|sh" "wget|sh")
for pattern in "${BLOCKED_PATTERNS[@]}"; do
if echo "$CMD" | grep -qi "$pattern"; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Blocked: dangerous command detected"
}
}'
exit 0
fi
done
exit 0
Then reference it in your settings:
{
"type": "command",
"command": "bash .claude/hooks/block-dangerous.sh"
}
This is the kind of hook that you should have, and it only needs to be set up once.
4. Auto-Stage Files After Claude Edits Them
This is a small hook that makes a big difference in your git workflow.
Every time Claude writes or edits a file, this hook runs
git addon that file. Your changes stay staged and ready to commit without you having to track what Claude modified.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "git add \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
The $CLAUDE_TOOL_INPUT_FILE_PATH variable gives you the exact file Claude just touched, and git add stages it immediately.
This pairs well with the auto-format hook from earlier. Prettier formats the file, then git stages it. Both run on
PostToolUsewith theWrite|Editmatcher, and they execute in parallel.
Note — if you’re working on a feature branch and want to review changes before staging, you might skip this hook. But for solo projects or quick prototyping, it removes one more manual step from your flow.
5. Log Every Tool Call to a File
As a Claude Code beginner, it is hard to understand how it works. It reads files, runs commands, writes code, but you don’t always see the full process.
A simple logging hook captures every tool call to a local log file. You can review it later to see what Claude did during a session.
#!/bin/bash
# .claude/hooks/log-tool-call.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Tool: $TOOL" >> .claude/hooks/tool-calls.log
echo "$INPUT" | jq '.tool_input' >> .claude/hooks/tool-calls.log
echo "---" >> .claude/hooks/tool-calls.log
Add it to your settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/log-tool-call.sh"
}
]
}
]
}
}
The empty matcher means it fires on every tool call — Bash, Write, Edit, Read, all of them. Each entry gets a timestamp, the tool name, and the full input.
After a session, open .claude/hooks/tool-calls.log and you'll see something like:
[2026-05-15 10:23:01] Tool: Write
{"file_path": "src/index.ts", "content": "..."}
---
[2026-05-15 10:23:04] Tool: Bash
{"command": "npm run build"}
---
This is one of the best learning Claude Code hooks for beginners. You start learning how Claude approaches problems
6. Protect Sensitive Files from Being Read or Modified
Some files should always be protected: your .env file with API keys, production config, and credentials, as quick examples
This
PreToolUsehook blocks Claude from reading, writing, or editing files that match a list of protected patterns.
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
# Get the file path based on the tool type
case "$TOOL" in
Read) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path') ;;
Write) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path') ;;
Edit) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path') ;;
Bash) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.command') ;;
*) exit 0 ;;
esac
PROTECTED_FILES=(".env" ".env.local" ".env.production" "secrets.json" "credentials.json")
for protected in "${PROTECTED_FILES[@]}"; do
if echo "$FILE_PATH" | grep -q "$protected"; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Protected file - access blocked by hook"
}
}'
exit 0
fi
done
exit 0
Add it to your settings with a broad matcher:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/protect-files.sh"
}
]
}
]
}
}
The hook checks the file path against a list of protected filenames. If there’s a match, it denies the operation. Claude gets the reason and moves on without accessing the file.
Notice we also check
Bashcommands. This catches cases where Claude might try tocat .envorgrepthrough your secrets via the shell.
You can customize the PROTECTED_FILES array to match your project. Add database configs, SSH keys, or any file you want locked down.
7. Force Claude to Run Tests Before Stopping
This is my favourite hook on this list, it will teach you the most important concept in Claude Code hooks — exit code 2.
When you ask Claude to build a feature, and it writes the code, says “Done!”, and stops, but the tests are failing.
The Stop hook fires every time Claude finishes. If your hook returns exit code 2, Claude is forced to keep working. It cannot stop until your conditions are met.
#!/bin/bash
# .claude/hooks/run-tests-before-stop.sh
INPUT=$(cat)
# Check if there's a test script in package.json
if [ ! -f "package.json" ]; then
exit 0
fi
# Run the tests
npm test 2>&1
TEST_EXIT=$?
if [ $TEST_EXIT -ne 0 ]; then
echo "Tests are failing. Fix them before completing the task."
exit 2 # Block Claude from stopping
fi
exit 0 # Tests passed, Claude can stop
Add it to your settings:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/run-tests-before-stop.sh"
}
]
}
]
}
}
The flow works like this:
- Claude finishes a task and tries to stop
- The hook runs your test suite
- If tests pass — exit code 0 — Claude stops normally
- If tests fail — exit code 2 — Claude sees the error output and keeps working to fix them
This creates a feedback loop where Claude self-corrects. It writes the code, the hook checks the tests, and if something is broken, Claude goes back and fixes it.
Final Thoughts
These 7 hooks cover the fundamentals: formatting, notifications, safety, logging, file protection, and self-validation. They use command hooks, which handle most workflows.
But Claude Code hooks go deeper. Once you’re comfortable with these, explore:
- Prompt hooks — where a separate Claude model evaluates a condition and returns a yes/no decision
- Agent hooks — where a subagent with access to tools like
Read,Grep, andGlobverifies conditions before returning a result - HTTP hooks — where events get sent to external services like Slack, Discord, or a custom dashboard
***SessionStart*— for injecting project context and environment variables at the start of every session
Which other Claude Code hooks do you think fit in this list? Let me know in the comments below.
메타데이터
- post_id
- 78ca29902b19
- slug
- 7-claude-code-hooks-most-developers-overlook-especially-beginners-78ca29902b19
- url
- https://medium.com/@joe.njenga/7-claude-code-hooks-most-developers-overlook-especially-beginners-78ca29902b19
- canonical_url
- https://medium.com/@joe.njenga/7-claude-code-hooks-most-developers-overlook-especially-beginners-78ca29902b19
- author_url
- https://medium.com/@joe.njenga
- status
- ok
- fetched_at
- 2026-06-09 15:37:30