Automating Your Workflow With Claude Code Hooks

2026-07-11 · 6 min

There's a category of instruction you keep giving Claude Code that it keeps half-remembering. "Run the formatter after you edit a file." "Don't touch the production database." "Ping me when you're done so I can review." You put it in CLAUDE.md, and it works — most of the time. Then, three tasks deep into a session, the model is focused on something else and forgets. The formatter didn't run. The commit went out unformatted.

The problem isn't that Claude is careless. It's that you asked a language model to guarantee a deterministic behavior. Those are different jobs. Hooks are the feature that lets you stop asking and start enforcing.

What a hook actually is

A hook is a shell command that Claude Code runs automatically at a specific point in its lifecycle. You register the command in your settings, tie it to an event, and from then on the harness — the program that runs Claude — executes your command whenever that event fires. The model isn't in the loop for that step. It doesn't decide whether to run the hook, and it can't forget to.

That single property is the whole point. When you write a rule into CLAUDE.md, you're adding text to the model's context and hoping it acts on it. When you write a hook, you're adding a step to the machinery. One is a request; the other is a guarantee.

Concretely, a hook is just a program that:

  • receives structured information about what just happened (which tool ran, what file was touched, what command was about to execute), and
  • does something with it — run a formatter, write a log line, send a notification, or signal that an action should be blocked.

Because it's an ordinary shell command, you can write it in whatever you already know: a one-liner, a Bash script, a Python file, anything your machine can execute.

The lifecycle points you can hook

Claude Code exposes a set of events over the course of a session. The exact names and configuration keys are in the official documentation, and you should check them there before you wire anything up — but conceptually, the useful ones fall into a few buckets:

  • Before a tool runs. Fires after Claude decides to use a tool (run a shell command, edit a file) but before it actually happens. This is your interception point — the only place you can inspect an action and stop it.
  • After a tool runs. Fires once a tool has completed. This is where you react to a change that already landed: a file was written, so format it; a command finished, so record it.
  • When Claude finishes responding. Fires when the model hands the turn back to you. Good for "I'm done" signals.
  • When you submit a prompt, when a session starts, and other boundaries. Useful for injecting context, logging, or setup.

Most hooks can also be scoped with a matcher so they only run for the tool you care about — for example, only after file edits, or only before shell commands. That keeps a formatter from firing on every unrelated action.

Three hooks worth setting up first

The clearest way to understand hooks is to look at what people actually build with them.

1. Format after an edit

Register a hook on the "after a tool runs" event, scoped to file-editing tools. When Claude writes or edits a file, the harness runs your formatter against it. Prettier, Black, gofmt, cargo fmt — whatever your project uses.

The result: every file Claude touches comes out formatted, every time, with zero reliance on the model remembering to do it. Your diffs stay clean and your CI's format check stops flagging the agent's work. A sketch of the logic:

#!/usr/bin/env bash
# Runs after Claude edits a file. Reads event data from stdin,
# extracts the edited path, formats it. (Field names: see the docs.)
file_path=$(jq -r '.tool_input.file_path // empty')
[ -n "$file_path" ] && npx prettier --write "$file_path"

The exact JSON shape the hook receives is documented — don't guess at field names, read them off the current schema.

2. Block a dangerous command

This is the one that changes how much you trust an agent with real access. Register a hook on the "before a tool runs" event, scoped to the shell tool. Your script inspects the command Claude is about to execute and decides whether to allow it.

A hook can signal a block: instead of just observing, it tells the harness to refuse the action and hand a message back to the model explaining why. Claude then sees the rejection and adapts, rather than the command executing. That means you can hard-stop things like rm -rf on a protected path, a git push --force to main, or any command touching a production connection string — deterministically, regardless of what the model was about to do.

#!/usr/bin/env bash
command=$(jq -r '.tool_input.command // empty')
if echo "$command" | grep -qE 'rm -rf /|drop database|--force.*origin/main'; then
  echo "Blocked: destructive command intercepted by policy hook." >&2
  exit 2   # exit code 2 is the one that signals a block here — not just any non-zero; confirm in the docs
fi

The precise mechanism for blocking — which exit code counts, or whether to emit a structured JSON decision instead — is a detail the documentation spells out exactly, and the codes don't all behave the way Unix convention would suggest. Get it from there. The concept to hold onto is that a pre-tool hook is the one place where your code, not the model's judgment, has the final say on whether an action proceeds.

3. Notify when Claude stops

Register a hook on the "Claude finished responding" event that fires a notification — a desktop alert, a terminal bell, a Slack message, a sound. Now you can kick off a long task, switch to something else, and get pulled back the moment it needs you instead of babysitting the terminal.

#!/usr/bin/env bash
osascript -e 'display notification "Claude finished" with title "Claude Code"'

None of these three are clever. That's the appeal. They're small, boring, reliable pieces of automation — and reliability is exactly what you couldn't get by putting the same instruction in a prompt.

Where hooks fit alongside skills and subagents

If you've used skills or subagents, it's worth being precise about the difference, because they solve opposite halves of the problem.

Skills and subagents shape what the model does — they give Claude better procedures, a second opinion, more discipline. But they still run through the model's judgment, which means they're probabilistic. That's the right tool when the task genuinely needs intelligence: planning a change, reviewing a diff, deciding how to refactor.

Hooks are for the opposite case: the behavior that should not depend on judgment at all. Formatting isn't a decision. "Never run this command against prod" isn't a decision. Those are policies, and policies want a mechanism, not a model. When you find yourself repeating an instruction and wishing it were simply guaranteed, that's the signal to reach for a hook instead of another line in CLAUDE.md.

Used together, the split is clean: the model does the thinking, the harness enforces the invariants.

Where the configuration lives

Hooks are configured in Claude Code's settings JSON, and they can live at the user level or be committed with a project so your whole team shares the same guardrails. The event names, the matcher format, the structure of the data each hook receives, and the exact blocking contract are all specified in the official docs — and they're the kind of detail you want to copy exactly rather than reconstruct from memory, so start there. If you'd like working examples to adapt rather than a blank file, the claude-code-starter repo has example hook configs you can lift and modify.

Conclusion

Hooks close the gap between "I told Claude to do this" and "this always happens." By moving a behavior out of the prompt and into the harness, you convert a hopeful instruction into a hard guarantee — formatting that always runs, dangerous commands that can't slip through, notifications that never get skipped. Start with one small, boring hook that removes a thing you're tired of repeating. Once you feel the difference between asking and enforcing, you'll spot a dozen more places it belongs.

Related guides

Set up Claude Code the right way

Start free with the open-source starter — a ship skill, an adversarial-reviewer subagent, a bug-hunt workflow, and a CLAUDE.md template. Want the full kit? The Pro Pack adds 6 skills, 3 subagents, 3 workflows, 4 templates and a handbook.

Free starter on GitHub Get the Pro Pack — $39