●PRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular price●PARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline management●TRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industries●BETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during September●LIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from today●RELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yet●PRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular price●PARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline management●TRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industries●BETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during September●LIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from today●RELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yet
Claude Code Hooks: A Field Guide to the 8 Core Hooks — and the 33-Event Catalog They Now Live In
Claude Code hooks are powerful, but most people give up before figuring out which event does what. Here's the field guide I wish I had when I started — six months of running hooks in production, distilled.
I spent six months running Claude Code in production before I truly understood hooks. The documentation covers what they do, but not why you'd pick one over another in a real scenario. Halfway through, I realized I'd been setting up hooks wrong the whole time: two hooks fighting each other, one never firing because I'd misconfigured the timing, another creating an infinite loop.
The frustration wasn't the hooks themselves—it was the mental model. I was treating them like simple on/off switches when they're actually a control flow system that governs the entire conversation between you, Claude Code, and your tools.
This guide is for people who got hooks working once, then broke something and gave up. Or people who want to avoid that entirely. I'll walk through every hook type, show you when to use it, and reveal the three production bugs I hit that taught me the hard way.
The Design Philosophy Behind Hooks
Claude Code hooks aren't just event listeners. They're the connective tissue between what you ask Claude to do and what actually gets executed.
Think of the traditional flow: user prompt → code generation → done. One direction, no feedback loop.
Hooks create: user ⇄ Claude Code (with hooks) ⇄ tools (git, npm, shell, API calls). Bidirectional. The hook system manages that conversation. Each hook type handles a specific phase.
The first principle: separate concerns. Don't put PostToolUse logic in PreToolUse. Don't use Stop to handle single-tool failures. Mixing them is where everything breaks.
The second: hooks are synchronous by default, but support async. If you put a Promise in a hook, Claude Code will wait. But wait too long and the session times out. The balance is tight—that's our first production trap.
The third: hooks fire in sequence, not in parallel. Misunderstanding this sequence is the most common footgun. Get this wrong and you'll spend hours debugging why a value is undefined or why a tool runs twice.
The fourth: each hook phase has a different responsibility. Don't make SessionStart do what SessionEnd should do. Don't make PreToolUse handle post-execution validation. This separation is what makes debugging possible.
The 8 Core Hooks: A Reference Map
A quick note on scope before the table. When this article was first published, these eight were the main events hooks offered. The catalog has grown a lot since — the official reference now lists 33 events as of late August 2026 — and I cover how to navigate that expansion in two later sections. The eight below are still the backbone, and everything else is easier to place once you know them.
Hook
Fires
Responsibility
Common Use
SessionStart
Immediately after session begins
Initialize environment
Set up dependencies, env files, initial checks
UserPromptSubmit
When user sends a message (before processing)
Pre-filter and enrich input
Detect code, validate syntax, security checks
PreToolUse
Right before a tool executes
Validate preconditions
Security gates, permission checks, state validation
PostToolUse
Right after a tool executes
Verify results
Confirm success, handle errors, log outcomes
Notification
Anytime, independently
Send external messages
Slack notifications, email alerts, webhooks
Stop
When deciding whether to end session
Evaluate completion
Auto-stop on goal achievement, cleanup
SubagentStop
When a subagent finishes
Collect and pass results
Feed subagent output to parent workflow
SessionEnd
Before session closes
Final cleanup
Generate reports, archive logs, notifications
This table is deceptive because it makes them look independent. They're not. Understanding the firing sequence is the foundation of hooks that actually work.
One note before we go deeper: the JSON in this guide is a conceptual pseudo-schema, written to make each hook's responsibility obvious. The format you actually put in your settings file is different, and I show it in full later under "How the Config File Actually Looks." Hold the map of responsibilities in your head first; we'll wire it to the real syntax afterward.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦The real hooks config format in settings.json — matcher, command, the stdin JSON contract, and exit code 2 to block — not the conceptual pseudo-schema
✦A map of the hook catalog as of August 2026 — 33 events — plus working PreModelSwitch / PostModelSwitch configs (v2.1.251) that turn model switches into enforceable policy
✦Measured per-tool overhead that hooks add, and the latency budget that keeps them from getting in the way
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
UserPromptSubmit — Every time the user sends a message
PreToolUse — Before each tool call
PostToolUse — After each tool call (on success)
Notification — Any time during the flow, runs in parallel
Stop — Evaluated at completion checks
SessionEnd — Once, at the very end
Subagents have their own sequence within the parent sequence.
This ordering matters more than anything else. Skip this section and you'll spend days debugging mysterious failures.
The reason I emphasize this: I once set environment variables in PreToolUse, expecting them to be available in PostToolUse. They weren't. Spent three hours before realizing they're different processes—environment variables don't cross boundaries.
PreToolUse vs PostToolUse: The Foundational Distinction
You'll use these two constantly. Getting them right means 80% of your automation works.
PreToolUse: Runs before the tool. Use it for "should I even run this?"
Example: before running npm install, check if package-lock.json is corrupted. If it is, regenerate it first. If you don't do this PreToolUse check, npm will silently install slightly wrong versions, and your app won't break for weeks.
When you push to a GPG-protected branch, PreToolUse verifies your key exists before attempting the push. PostToolUse verifies the remote actually accepted your signature.
UserPromptSubmit: Input Processing and Security
This fires when the user sends a message, before Claude Code processes it. Use it to clean, validate, or enrich user input.
Real example: users sometimes paste code snippets. That code might use imports that aren't installed. Catch that before Claude Code tries to run it.
Real injection example: user pastes SQL from a bug report.
Query: SELECT * FROM users WHERE id = 'x' OR '1'='1'
A good UserPromptSubmit hook detects this pattern and alerts before Claude Code tries to execute it. This has saved me several times when users pasted problematic queries from bug reports.
You can also use this hook to inject context. For instance, if the user mentions "fix the login flow," you can automatically prepend the current auth.ts file and the test file to the context, saving the user from having to explicitly ask.
Stop vs SubagentStop: Don't Confuse These
Stop: End the entire session.
Use it when the goal is accomplished. All tests pass, coverage is above 80%, no errors. Stop here.
This hook is useful for fully automated workflows. You set a goal, Claude Code works toward it, and once the goal is met, the session closes automatically. No user intervention needed.
SubagentStop: End just one subagent, then feed its results back to the parent.
Use it for parallel work. "Backend development subagent is done. Send results to frontend development subagent."
SubagentStop lets you orchestrate these workflows. When backend finishes, notify the integration subagent. When frontend finishes, trigger E2E tests.
Confusing these means:
Using SubagentStop when you meant Stop → session keeps running, extra unexpected work happens
Using Stop when you meant SubagentStop → parallel work stops prematurely
I learned this the hard way. I had a backend subagent finish, but used Stop instead of SubagentStop. The parent session immediately closed without running the frontend integration tests. Took me an hour to realize the order was wrong.
SessionStart and SessionEnd: Environment Bookends
SessionStart: Runs first, before anything else. Initialize the environment.
SessionStart is where you set up everything Claude Code needs before touching your code. Check Node version, install dependencies, generate environment files, run security audits.
The power here is that Claude Code never makes the mistake of "oh, I needed Node 18 but only 16 is installed." You're baking in those requirements upfront.
SessionEnd: Runs last, before the session closes. Cleanup and reporting.
SessionEnd is where you clean up and report results. Generate a summary of what was done, send it to stakeholders, archive logs for auditing, delete temporary files.
This is useful for compliance. If your workflow touches production or sensitive data, SessionEnd lets you ensure all audit trails are preserved before the session closes.
Notification: Fire and Forget External Communication
Send data to external systems. Slack, email, webhooks—doesn't matter. This hook fires independently and doesn't block the session.
The eight hooks above were the whole story when I first wrote this guide. They no longer are. The official hooks reference now lists 33 events. That number looks intimidating, but most of the additions are refinements that fill gaps between the core eight — the design philosophy hasn't moved.
The documentation organizes events into three cadences: once per session (SessionStart / SessionEnd), once per turn (UserPromptSubmit / Stop / StopFailure), and once per tool call (PreToolUse / PostToolUse). Hold onto those three layers, then read each new event as a refinement of one of them, and the map shrinks back down to something you can keep in your head.
Model switches, command expansion, and MCP elicitation as new control points
My working advice: design with the core eight first, and only come back to this table when you notice a moment you want to observe that none of the eight firing points covers. Of all the additions, the only ones I touch routinely are the model-switch pair below and PostToolUseFailure — and that has been enough. Not trying to use everything is what keeps a hook setup maintainable.
PreModelSwitch / PostModelSwitch: Turning Model Switches into Policy
Of everything added to the catalog, the pair that changed my day-to-day operations most is the model-switch hooks, introduced in v2.1.251 (released August 28, 2026).
The mechanics are straightforward. PreModelSwitch fires right before Claude Code applies a model switch that you or a client requested, and exit code 2 blocks the switch. PostModelSwitch fires after the session's model changes — including changes Claude Code makes on its own, such as restoring the model when you resume a session. Unlike other events, the stdin JSON carries from_model and to_model instead of a single model field, and the matcher is evaluated against the canonical name derived from to_model, so you can scope a hook to "only when switching to a specific model" entirely in config.
Why does this matter? I run content generation and maintenance for several sites as scheduled tasks, with a different model assigned to each stage of the pipeline. Until now that allocation was a promise written into prompts — nothing enforced it, and nothing told me when it was broken. With weekly usage limits being restructured on September 14, 2026, I wanted switches to expensive models gated by machinery rather than convention. With hooks, that takes one config block and two small scripts.
There is one asymmetry worth internalizing: only PreModelSwitch can block. By the time PostModelSwitch fires, the switch has already happened — exit code 2 there just surfaces your stderr as an error notice and nothing rolls back. What PostModelSwitch can do is inject context: its stdout is added as context Claude can see, delivered with the next request after the switch. That makes it the right place for automated handoff notes like "the model just changed — keep outputs terse for the rest of this session." Block with Pre, inform with Post. It's the same division of labor as PreToolUse / PostToolUse, so the instincts you built on the core eight transfer directly.
Three Production Traps I Hit (And How to Avoid Them)
Trap 1: Misunderstanding Hook Firing Order
Hooks don't fire simultaneously. They fire in a strict sequence:
SessionStart
UserPromptSubmit
PreToolUse
PostToolUse (only if tool succeeds)
Notification (parallel)
Stop (evaluated)
SessionEnd
I once set an environment variable in PreToolUse and tried to use it in PostToolUse. It was undefined. Why? Because PreToolUse runs in one subprocess and PostToolUse runs in a different subprocess. Environment variables don't cross subprocess boundaries.
If your hook makes an async call (API request, database query, CI status check), Claude Code will wait. But only for a configurable limit. Exceed it and the session times out.
I wrote a hook to wait for CI to finish after a git push. 30 seconds in the test repo, no problem. On a real project with a 20-minute CI pipeline, the session timed out.
The on_timeout action is critical. If CI is slow, don't fail the hook—keep going and notify later via a separate async process.
Another approach: split the work. PreToolUse triggers the CI build. PostToolUse checks if it's done, but doesn't wait. A separate background job checks later and notifies.
Everything above was a conceptual diagram of responsibilities. Here's the shape that actually runs in my setup (Claude Code v2.x). Key names have shifted between versions, so confirm the details against your own installation — but the skeleton is stable.
Hooks live under the hooks key of .claude/settings.json (per project) or ~/.claude/settings.json (per user), as an array per event name. The important shift: a hook's body isn't a JSON declaration, it's a shell command. Your decision logic lives inside your own script.
matcher is a regex against the tool name — Bash, or Edit|Write for several, or an empty string for all tools. The exact-match behavior for hyphenated tool names was tightened in a specific version, so if a hook goes silent right after an update, suspect that first: when an update stops your hook from firing (v2.1.195 matcher change).
The script receives a JSON payload on stdin. The tool name and its arguments are in there.
#!/usr/bin/env bash# guard-git-push.shinput=$(cat) # read the full JSON from stdincmd=$(echo "$input" | jq -r '.tool_input.command // ""')if echo "$cmd" | grep -qE 'git push .*(-f|--force)'; then echo "Force-pushing to main is blocked. Consider --force-with-lease." >&2 exit 2 # exit 2 = block this tool call, feed the reason back to Claudefiexit 0 # exit 0 = allow it through
The single biggest difference from the declarative schema is how you signal a block. Exit with code 2 and whatever you wrote to stderr is handed back to Claude while the tool call is stopped. Exit 0 and it sails through. Once you internalize that two-value contract, every gate, validation, and notification described earlier as "pseudo-schema" is just an ordinary shell script. Which also means debugging a hook comes down to one question: can you run that script by hand? Pipe a sample JSON into it before you ever blame the config file.
Measuring Hook Overhead
It's easy to miss, but every hook spawns a separate process. Each tool call pays a small startup cost, and those costs stack. You won't feel it day to day — until you hang something heavy off PostToolUse.
I measured this on the CI integration for a wallpaper app I build solo. For the same git commit, I compared three setups — no hook, a lightweight validation script, and running npm audit on every call — and timed the added latency per call. Median values on my machine, for reference:
Setup
Added time per call (median)
Felt like
No hook
0 ms
baseline
Lightweight script (branch in jq, exit fast)
~40–80 ms
unnoticeable
PostToolUse running npm audit every time
~1,800–3,200 ms
a clear wait
A few tens of milliseconds of process startup never register in a conversation. What bites is running anything that touches the network or disk on every call. That npm audit above only needed to run right after an install, not on every command.
My operating rule of thumb: keep a synchronous hook's added time under 200 ms. Anything heavier gets its firing narrowed with matcher and conditions, or pushed async so I only receive a notification. The faster a hook is, the more invisible it stays while always on — and that quietness is exactly what lets you keep it running instead of ripping it out.
Operating Hooks in Production
Test first. Hooks that touch git or npm can silently break things. Test in a dummy project first. Create a test repository, set up your hooks, run a few scenarios, and verify behavior matches expectations.
Log everything. When something goes wrong, you need to know which hook fired, in what order, and what it did.
Version-control hook configs. Commit your hook definitions to git. Later you'll ask, "when did this hook get added?" Git history answers it.
git add .claude/settings.jsongit commit -m "feat: Add security gate for main branch pushes"
Hooks are the lever that turns Claude Code from a code generator into an automated workflow engine. The mental model is the hard part. Once you have it—sequence, dependencies, separation of concerns—the execution is straightforward. Start small, test thoroughly, and you'll have something powerful.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.