Claude Code hooks: the 4 I kept, the 2 I deleted (2026)
I ran Claude Code hooks daily for a month. Four earned their place, two got deleted, and one quietly made my workflow worse. The honest field log.
Updated on July 11, 2026

On this page
Quick Answer
Claude Code hooks are shell commands that fire at fixed points in the agent's lifecycle, like right before a tool runs or right after a file gets edited. After running them daily through June and July 2026, I keep four (a formatter, a bash guardrail, a typecheck-on-stop, and a desktop notification) and I deleted two that added more friction than they removed. The rule that survived: add a hook to kill one specific recurring annoyance, not because the docs list a dozen events you could wire up.
I have been living in Claude Code for months now, and hooks are the part of it I was slowest to touch. They looked like config-file busywork. Then I hit the same annoyance for the third time in a week, wired up one hook, and the annoyance disappeared. So I went the other way and added a pile of them. Most of that pile is gone now. This is the honest field log of what stuck.
One of them now doubles as a security guardrail after GhostApproval showed how a symlink can turn an approved write into an SSH-key overwrite.
What a Claude Code hook actually is
A hook is a command you register in settings.json that Claude Code runs automatically when a lifecycle event fires. You match an event to a shell command, and the agent runs your command at that moment. The events I actually reach for:
PreToolUse: fires before the agent runs a tool. It can block the tool by exiting with code 2.PostToolUse: fires after a tool finishes, so you can react to an edit or a command.Stop: fires when the agent finishes its turn and hands control back to you.Notification: fires when Claude wants your attention, like a permission prompt.
There are a few more (UserPromptSubmit, SubagentStop, PreCompact, SessionStart), and a viral Reddit post counts "23 hooks," but that number comes from multiplying events by matchers. In practice you are working with a handful of events. The skill is not knowing all of them. It is knowing which one earns a slot in your config.
Here is the shape, so the rest of this makes sense:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"" }
]
}
]
}
}
That is the whole idea. An event, a matcher, a command. Now the field notes.
The four hooks I kept
Format on every edit
A
PostToolUse hook matched to Edit|Write that runs the formatter on whatever file Claude just touched. On this repo that is Prettier; on a newer one I use Biome because it is faster and the agent never has to wait on it. This is the single highest-value hook I run. Before it, half my review time was noise: the agent writes fine logic in slightly wrong whitespace, the diff looks scary, I squint at reformatting instead of at behavior. Format on write and the diff is only the change. Ten seconds of setup, paid back the first afternoon.
A bash guardrail that can say no
A PreToolUse hook matched to Bash that inspects the command and exits code 2 (which blocks it) if it matches a small denylist: rm -rf outside the project, git push --force to anything that is not my own feature branch, anything touching .env. I am not worried the agent is malicious. I am worried it is confidently wrong at 2x speed. This hook has blocked exactly four commands in a month. Two were genuinely bad. That is a good trade for a fifteen-line script.
Typecheck when the agent stops
A
Stop hook that runs tsc --noEmit plus ESLint on the changed files when the agent finishes its turn. The point is not to auto-fix. The point is that the moment Claude says "done," I already know whether the types hold, before I have read a single line. When it fails, I feed the output straight back in. This turned my review loop from "read everything, then find the type error" into "let the machine find the type error, then read the parts that matter."
Tell me when you need me
The Notification hook is the least technical and the one I would give up last. It pings a desktop notification (a two-line script calling terminal-notifier on my Mac) whenever Claude is waiting on a permission prompt or has stopped. I run agents in a background terminal while I do other work. Without this hook I context-switch back every ninety seconds to check. With it, I stay in the other window until it actually needs me. It bought back more real focus time than any of the clever ones.
The two I deleted
The context-injector
A UserPromptSubmit hook that pulled in "relevant" project context and prepended it to every prompt I sent. On paper, free context. In practice it fought me. It injected stale notes, it made short prompts expensive, and worst, it hid what the agent was actually seeing, so when the output was off I could not tell if my prompt was bad or the injection was. I deleted it and went back to putting durable context in a short instructions file, which is visible and versioned. If you have read my note on cutting my AGENTS.md down, you know how that ended: less context, better results.
The auto-committer
A PostToolUse hook that committed after every successful edit. I wanted a clean undo trail. I got a git log with forty commits titled "wip" for one feature, and a history I could not read or bisect. Auto-commit optimizes for the wrong thing. I want few, meaningful commits I write on purpose, not a keystroke ledger. Gone in two days.
The one that made things worse
This is the part the "set up all the hooks" posts skip. I ran a PreToolUse hook on Edit that gated every single edit behind a full lint pass, blocking the edit if the file would not lint clean. It sounded disciplined. It was miserable. The agent works in intermediate states: it writes a function, then imports it, then wires it up. Blocking mid-sequence because step one does not lint yet means the agent thrashes, retries, and burns tokens fighting my hook instead of writing code. Lint belongs at Stop, when the work is supposed to be whole, not at PreToolUse, when it is deliberately half-done. Same tool, same lint command, completely different outcome depending on which event you attach it to. That is the actual lesson of hooks: the event you pick matters more than the command you run.
How I decide whether a hook earns its place
My whole heuristic now fits in three questions:
- Is there a specific annoyance I have hit at least three times this week? If not, no hook. Hooks are for proven, repeated friction, not hypothetical tidiness.
- Does it belong on a "the work is paused" event (
Stop,Notification) or an "in the middle of working" event (PreToolUse,PostToolUse)? Anything that judges completeness goes on a pause event. Anything reactive and cheap can go inline. - When it fails, is the failure loud and obvious, or silent and confusing? The context-injector failed silently, so it went. The guardrail fails loud (it tells you it blocked a command), so it stayed.
If a hook cannot pass all three, I leave the event unhooked. An empty slot is not a missed opportunity. It is one less thing quietly shaping the agent's behavior in a way I have to remember later.
For a fuller catalog of events and JSON schema, the official hooks reference is the source of truth, and the community claude-code-hooks-mastery repo is a good place to see every event wired up before you decide which ones you actually want. My advice is to read them as a menu, not a checklist. This is the same restraint that ran through my Claude Code subagents cleanup and my three things I deleted: the config you keep small is the config you can still reason about in three months.
FAQ
Does Claude Code have hooks?
Yes. Hooks are shell commands you register in settings.json that run automatically at lifecycle events like PreToolUse, PostToolUse, Stop, and Notification. They are event-driven automations, not part of the prompt.
What is a PreToolUse hook?
A command that runs right before Claude Code executes a tool, such as a bash command or a file edit. If it exits with code 2, it blocks the tool. It is the right place for guardrails, and the wrong place for anything that checks whether work is finished.
What is a Stop hook?
A command that runs when the agent finishes its turn and hands control back to you. It is where I run typecheck and lint, because the work is meant to be complete at that point.
How many Claude Code hooks are there?
There are a handful of lifecycle events (around eight). Higher counts you see online come from multiplying events by matchers. You rarely need more than three or four wired up.
Are Claude Code hooks worth setting up?
Only for a specific annoyance you have hit repeatedly. A formatter on edit and a notification when the agent needs you pay off immediately. Wiring up events speculatively usually creates friction you delete later.
Can a hook block a command Claude wants to run?
Yes. A PreToolUse hook that exits with code 2 blocks the tool call. I use one to stop force pushes, recursive deletes outside the project, and anything touching secrets.
Sources
- Claude Code hooks reference, Anthropic, 2026: https://code.claude.com/docs/en/hooks
- claude-code-hooks-mastery (disler), GitHub, 2026: https://github.com/disler/claude-code-hooks-mastery
Written by
M. PatelBackend dev. Writes when something breaks. Currently shipping a B2B agent product with one cofounder.
Frequently asked questions
Does Claude Code have hooks?
Yes. Hooks are shell commands you register in settings.json that run automatically at lifecycle events like PreToolUse, PostToolUse, Stop, and Notification. They are event-driven automations, not part of the prompt.
What is a PreToolUse hook in Claude Code?
A command that runs right before Claude Code executes a tool, such as a bash command or a file edit. If it exits with code 2, it blocks the tool. It is the right place for guardrails, and the wrong place for anything that checks whether work is finished.
What is a Stop hook?
A command that runs when the agent finishes its turn and hands control back to you. It is where I run typecheck and lint, because the work is meant to be complete at that point.
How many Claude Code hooks are there?
There are a handful of lifecycle events (around eight). Higher counts you see online come from multiplying events by matchers. You rarely need more than three or four wired up.
Are Claude Code hooks worth setting up?
Only for a specific annoyance you have hit repeatedly. A formatter on edit and a notification when the agent needs you pay off immediately. Wiring up events speculatively usually creates friction you delete later.
Can a hook block a command Claude wants to run?
Yes. A PreToolUse hook that exits with code 2 blocks the tool call. I use one to stop force pushes, recursive deletes outside the project, and anything touching secrets.
Keep reading
Claude Code subagents: my 4 keepers, 3 I deleted (June 2026)
Seven Claude Code subagents written in a week. Four still run daily, three are in the trash. The YAML, the tool whitelists, the cost-per-invocation numbers, the three anti-patterns I won't repeat, and the honest carve-outs where subagents are the wrong tool.
Three things I deleted from my Claude Code setup this month (and one I almost did)
A short, honest June 2026 retro on what got cut from a working Claude Code config: the custom statusline, the over-engineered MCP wrapper, the autosave permission. Plus the one thing I almost killed and why I kept it.
Cursor vs Claude Code: an honest field log (2026)
A month of running Cursor and Claude Code daily on the same Next.js project. What each one is actually for, where they bit me, the cost difference, and why I kept both instead of picking a winner.


