Claude Code Hooks: Automate Your Agent Workflow

Claude Code hooks are shell commands the CLI runs automatically at defined points in an agent session, such as before or after a tool call, when the agent stops, or when a session starts. You configure them in your settings JSON, match them to specific events and tools, and Claude Code fires your command with the event data on stdin, which lets you enforce rules and automate steps the model would otherwise skip.
Most hook tutorials stop at "you can run a script." The useful version is about determinism: hooks turn "please remember to lint" from a hope into a guarantee the tooling enforces.
What are Claude Code hooks?
A hook is a command that runs on an event, not on the model's discretion. That distinction is the whole point.
The agent decides which tools to call, but hooks always run regardless of what the model chooses to do.
That makes them the right place for anything you cannot afford to leave to chance. Formatting, linting, secret scanning, and audit logging all belong in hooks, because you want them to happen every single time, not most of the time.
Hooks differ from skills and MCP servers in what they control. Skills give the model new instructions, and MCP servers give it new tools, while hooks govern the session itself.
If you are still mapping the pieces, Claude Code skills and Claude Code MCP cover the other two layers.
The model chooses tools; hooks are not optional. If a rule must hold on every run, encode it as a hook, not as a line in your prompt the model can forget under load.
Which events can Claude Code hooks fire on?
Claude Code exposes a set of lifecycle events, each firing at a specific moment. Here are the ones you will reach for most.
| Event | Fires when | Common use |
|---|---|---|
| PreToolUse | Before a tool runs | Block edits to protected paths |
| PostToolUse | After a tool succeeds | Auto-format or lint changed files |
| UserPromptSubmit | You submit a prompt | Inject context or redact secrets |
| Stop | The agent finishes responding | Run tests, send a notification |
| SubagentStop | A subagent finishes | Aggregate results |
| SessionStart | A session begins | Load project context |
| SessionEnd | A session ends | Write a session log |
Each event passes a JSON payload on stdin describing what happened, including the tool name and arguments where relevant. Your command reads that, does its work, and signals back through its exit code and output.
The full event reference lives in the Claude Code hooks docs.
The two you will use daily are PostToolUse for cleanup and PreToolUse for guardrails. Everything else is situational.
How do you configure a Claude Code hook?
You add hooks under a hooks key in your settings JSON. Each entry pairs a matcher (which tool or event) with one or more commands to run.
Here is a hook that formats and lints every file the agent edits or writes.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npm run lint --silent" }
]
}
]
}
}
The matcher is a pattern against the tool name, so Edit|Write catches both file-editing tools and ignores the rest. When the agent finishes an edit, Claude Code runs your command and the payload arrives on stdin, so a smarter script can read exactly which file changed and lint only that.
Guardrails work through the exit code on a PreToolUse hook. A non-zero exit blocks the tool call and feeds your stderr back to the model, so it learns why and adjusts.
#!/bin/bash
# Block edits to protected files
payload=$(cat)
file=$(echo "$payload" | jq -r '.tool_input.file_path // empty')
case "$file" in
*.env|*/secrets/*) echo "Refusing to edit protected file: $file" >&2; exit 2 ;;
esac
exit 0
Test a hook command by hand with a sample JSON payload piped to it before wiring it into settings. If it behaves in the shell, any failure inside Claude Code is almost always the matcher or the settings path, not your script.
Scope matters when you have several hooks on the same event. Claude Code runs every matching hook for an event, so a broad matcher plus a slow command on PostToolUse will tax every edit the agent makes.
Keep the hot-path hooks narrow and fast, and reserve heavier work for events that fire once, like Stop or SessionEnd.
One more detail worth knowing early: hooks read the settings at session start, so a change to your hook config does not take effect until you restart Claude Code. If a hook you just added does nothing, a restart is the first thing to try before you assume the matcher is wrong.
What are the best uses for Claude Code hooks?
The best hooks remove a class of mistake rather than automate a single task. Auto-formatting on PostToolUse means the model never leaves unformatted code, and you never review a diff full of whitespace noise.
Guardrails are the second big win. A PreToolUse hook that blocks edits to .env files, migration folders, or vendored code stops the agent from touching things it should not, without you watching every step.
This is far more reliable than asking nicely in a prompt.
Observability is the third. A hook on every tool call that appends to a log gives you a full audit trail of what the agent did, which is invaluable when a run goes sideways and you need to reconstruct it.
Hooks make agent behavior auditable, which matters more as agents take on bigger changes.
Notifications round it out. A Stop hook that pings your terminal or a webhook when a long run finishes means you can start a task, walk away, and get pulled back only when it is done.
Context injection is the subtler use that pays off over a whole project. A SessionStart or UserPromptSubmit hook can prepend the current git branch, the failing test names, or a short project convention note, so the agent begins with facts it would otherwise have to ask for or guess.
Because the hook runs deterministically, that context is present on every prompt without you retyping it. Used together, these four categories, cleanup, guardrails, observability, and context, turn a raw agent into one that matches how your team actually works.
How do hooks pair with a screen-context MCP server?
Hooks and MCP servers compose well: the server gives the agent a tool, and a hook can react when the agent uses it. This is where a point-and-speak capture workflow gets tidy.
PinVari is a native macOS app that runs a local MCP server on 127.0.0.1. You hold ⌥⌘A, circle any on-screen UI element, and speak; it resolves the exact accessibility element you circled, with its role, label, and frame and a confidence score, and exposes it to Claude Code through pinvari_next_instruction.
The agent gets a named element, not a screenshot to decode.
Hooks close the loop around that. A Stop hook can call pinvari_mark_done to clear a capture once the agent has acted on it, or a SessionStart hook can pull the next queued instruction so you begin each session with the bug already in context.
The design rationale for feeding an agent resolved screen context is in how a local MCP server gives an agent screen context.
Everything in that flow is on-device. Transcription and OCR use Apple frameworks, there are no API keys, and nothing is uploaded by default, so wiring it into your hooks does not send your screen anywhere.
PinVari is a one-time $39 at launch, with details on the pricing page.
The combination is the point. Hooks make the workflow deterministic, and a local MCP server gives the agent something precise to act on, so the automation is both reliable and grounded in what you actually pointed at.
FAQ
#
What are Claude Code hooks used for?
They run your own commands automatically on session events, so rules always execute regardless of what the model decides. Common uses are auto-formatting after edits, blocking edits to protected files, logging every tool call, and notifying you when a run finishes.
#
How do I add a hook in Claude Code?
Add a hooks key to your settings JSON, pair a matcher with a command, and Claude Code runs that command on the matching event with the payload on stdin. Test the command by hand with a sample payload first, then confirm the matcher fires by watching for its effect during a session.
#
What is the difference between hooks, skills, and MCP in Claude Code?
Hooks control the session and run deterministically on events, skills give the model new instructions and behaviors, and MCP servers give the model new tools it can call. They stack: a hook can react to a tool that an MCP server provided.
#
Can a Claude Code hook block a tool call?
Yes. A PreToolUse hook that exits with a non-zero code blocks the tool from running and passes its stderr back to the model, so the agent sees why it was stopped.
This is how you enforce guardrails like protecting secret files or migration directories.
#
Do Claude Code hooks slow down the agent?
Only by the time your command takes, since hooks run synchronously at their event. Keep frequently-firing hooks like PostToolUse fast, scope matchers narrowly, and push slow work to a Stop hook that runs once at the end rather than on every tool call.
#
Can hooks trigger actions in an MCP server?
Yes. A hook can call any command, including one that talks to a local MCP server, so a Stop hook can close out a resolved capture or a SessionStart hook can load the next queued instruction.
This composes the deterministic timing of hooks with the tools an MCP server exposes.
Hand your agent the exact element
PinVari resolves what you point at into a named, executable instruction — on-device, no keys, your own agent. One click inside PinVari connects Claude Code, Cursor, VS Code or Codex — or paste one CLI line from pinvari.com/connect.
PinVari → Connect → your agent (one click)Get PinVari — $39 →


