The 7 Claude Code Hooks I Use (and 20 You Can Too)
In my last piece on splitting AI workflows into two layers, I wrote about putting the deterministic work into Python scripts and leaving the judgment work to Claude. Same idea applies one layer deeper. Inside Claude Code itself, there is a feature called hooks that lets you wire code into the conversation loop. Stuff that runs on events, every time, without Claude having to remember.
If CLAUDE.md is where I write what I want to happen, hooks are how I make sure it actually happens.
The Problem Hooks Solve
LLMs forget. Not because they are broken. Because every response is a fresh prediction on top of whatever is in the context window right now. You can tell Claude "always do X at the end of every task," and it will, right up until a rule gets compressed out, the session resets, or the model decides something else takes priority in this turn.
I had a rule that said "always commit and push after a task." Worked great until it did not. Work ended up stranded on one machine while I moved to another. The fix was not a better rule. The fix was a hook that runs after every Bash tool call, checks if there has been a git push, and if so, syncs the other machines automatically. No Claude memory involved.
That is the shape of every hook worth writing. Something must happen every time X. Make sure it does.
What A Hook Actually Is
A hook lives in ~/.claude/settings.json. You pick an event. You pick a matcher (sometimes). You give it a shell command to run. That is the whole contract. When the event fires, Claude Code runs the command, and whatever the command prints to standard out lands inside Claude's context as a system reminder. Claude sees it, I see it, and Claude can act on it.
Simple plumbing. The power is in picking the right event.
The 7 Hooks I Use Every Day
Claude Code's documentation lists 27 hook events. I use 7 of them. Here is what each one does for me, with a real example from my own config.
1. SessionStart — Setting The Table
Fires when a session opens, with matchers for startup, resume, clear, and compact.
This is where I put everything Claude needs to know the moment I sit down. Which machine I am on. Whether voice is on. What Claude Code version is running, and whether it is behind latest. A capabilities briefing so Claude knows which integrations and tools are available. A warning if I already have too many sessions open.
Without SessionStart, every session starts with me briefing Claude. With it, Claude walks in with its tool belt on.
2. UserPromptSubmit — Decorating What I Say
Fires every time I hit enter to send a message.
Mine stamps a timestamp on the response (computed fresh by the hook, not guessed by Claude), injects the current git dirty status across my repos so Claude sees which ones have uncommitted work, reminds Claude how to speak through my TTS pipeline, and clears any stale voice nudge files.
The timestamp is the one that changed everything. Claude used to guess the time and get it wrong half the time. Now the hook writes the exact time into the prompt every turn. Never Guess, Always Compute, enforced by code.
3. PreToolUse — Gating What Claude Is About To Do
Fires before Claude calls a tool. Critically, it can block the tool call.
This is the hook that feels the most like programming Claude's reflexes. A few I block or redirect:
- A memory-gate hook on Write. If Claude is about to save something to my memory folder when the change really belongs in source code, the hook blocks the write and tells Claude to fix the code instead.
- A Gmail guard on the paid Gmail MCP read tools. My local Gmail CLI does the same thing for free. The hook blocks the MCP call and tells Claude to use the CLI.
- A voice-match hook on Bash when Claude tries to speak. It compares the spoken text against the on-screen text and blocks the call if they drift apart. So I never get a paraphrased voice that does not match what I am reading.
- A git-add guard on Bash, so a bulk git add -A gets blocked when more than one file is dirty. Atomic commits, enforced by code.
If I catch myself telling Claude the same thing twice, I reach for PreToolUse. Rules that must happen become code.
4. PostToolUse — Reacting To What Just Happened
Fires after a tool call succeeds.
My main use is the post-push sync. The moment Claude pushes to a git repo, the hook reaches into my other machines and pulls the same repo, so they all stay in sync without me thinking about it. I also fire an auto-nudge that starts a verbal reminder timer when Claude is waiting on me.
The pattern is "right after X, do Y." Things I would otherwise have to remember. I do not want to remember. I want the machine to remember.
5. Notification — Hearing The Bell
Fires when Claude Code surfaces a notification, with matchers for permission_prompt, idle_prompt, and a few more.
Mine plays a Glass sound every time Claude needs my permission. It also speaks any queued nudge context when an idle prompt comes up. Small, but it means I can walk away from the screen and still get pulled back the moment Claude needs me. My office is not quiet. The bell is not optional.
6. Stop — Starting The Waiting Timer
Fires when Claude finishes its turn and control is back to me.
When Stop fires, I kick off a waiting timer. If I walk away and do not come back, that timer escalates through verbal reminders at 20, 30, and 40 minutes. "Hey, you have a session waiting on the hooks post." It is the safety net for my ADHD. Claude cannot nudge me, because Claude is not running. The hook fires the moment Claude stops, and that is enough.
7. PreCompact — The Last Chance
Fires right before Claude Code compacts the context to save tokens.
I have auto-compaction turned off because I got burned by it. A rule about voice got compressed away mid-session and I spent an hour wondering why Claude sounded wrong. But I still keep the hook wired for when I manually run /compact. It prints a loud reminder. Save a checkpoint right now before the context gets squeezed. Write the topic journals. Save the last-session file.
Compaction loses information. It is a physics problem, not a trust problem. The hook forces the save.
The Other 20 You Can Reach For
I do not use these, but any of them could be the right tool on a given day. Full docs at code.claude.com/docs/en/hooks.md.
- UserPromptExpansion — when a typed slash command expands into a prompt.
- PermissionRequest — when a permission dialog appears.
- PermissionDenied — when auto-mode denies a tool call.
- PostToolUseFailure — when a tool call fails.
- SubagentStart and SubagentStop — when a subagent spawns or finishes.
- TaskCreated and TaskCompleted — when a task is created or completed.
- StopFailure — when the turn ends due to an API error (rate limit, auth, billing, and so on).
- TeammateIdle — when an agent teammate is about to go idle.
- InstructionsLoaded — when a CLAUDE.md or rules file loads.
- ConfigChange — when a config file changes mid-session.
- CwdChanged — when the working directory changes.
- FileChanged — when a watched file on disk changes.
- WorktreeCreate and WorktreeRemove — worktree lifecycle.
- PostCompact — right after compaction, the mirror of PreCompact.
- Elicitation and ElicitationResult — when an MCP server asks for user input.
- SessionEnd — when the session terminates.
Each one is a seam in the conversation loop where code can step in.
The Four Jobs Hooks Do
After writing 60 or so hook scripts, I started seeing a pattern. Every hook is doing one of four jobs.
- Block. Stop a tool call before it runs. Memory gate, Gmail guard, voice match, git-add guard.
- Decorate. Add information to what Claude sees. Timestamp, git dirty status, capabilities briefing.
- Enforce. Make a rule happen every time. Build number bump, clipboard reminder, no-raw-say.
- React. Do something after an event. Post-push sync, auto-nudge, idle notification.
If you are not sure which event to use, figure out the job first. Block happens PreToolUse. Decorate happens UserPromptSubmit or SessionStart. Enforce spans both. React happens PostToolUse, Stop, or Notification.
The Punchline
The deterministic article made this point at the app level. When a behavior must happen, use code, not a promise from the model. Hooks are the same principle, one layer deeper. They let you program the harness around Claude, so Claude does not have to remember.
I have written roughly 60 hook scripts now. Some are 3 lines. Some are 200. Each one started the same way. I found myself telling Claude the same thing twice. And instead of writing the rule into CLAUDE.md for the third time, I asked a different question: can this be a hook?
If it can, make it a hook. LLMs forget. Hooks do not.
Some links in this article are affiliate links. If you purchase through them, we may earn a commission at no extra cost to you. This helps support our content.
This article blends original content, AI-assisted drafting, and human oversight. How I write.
Stay Updated
Get notified when new content is published.
No spam. Unsubscribe anytime.