Skip to content
For your teamWhat one developer teaches an agent, a teammate’s agent can use.How it worksWhere a lesson starts and how it reaches every agent.Code reviewYour team’s knowledge, checked in every PR.SecurityWhat it sends, what it changes and how to leave.Book a call30 minutes with Roei, if you want a hand. Never required.
On your ownYour corrections follow you across agents and repos.Install itnpx harborloop init, in your repo.Every commandWhat the CLI does, and how to take it off again.
DocsPricingSign in
Start free →
For teamsFor individualsHow it worksCode reviewSecurityDocsPricingSign inStart free →
Home/Blog

Blog

  • Supermemory alternatives
  • PR review against AGENTS.md
  • Claude Code second brain
  • Why Copilot ignores it
  • Cursor Team Rules
  • Rules vs skills
  • Claude Tag vs Claude Code
  • GitHub Copilot Memory
  • Codex memories
  • Cursor memories
  • Claude Code auto memory
  • Claude Code team memory
  • Supermemory Company Brain alternatives
  • 3,905 files checked
  • Why Codex ignores it
  • Mem0 alternatives
  • Codex skills
  • Karpathy's LLM wiki
  • Auto mode
  • Memory or documentation?
  • Best company brain tools
  • Claude Code and AGENTS.md
  • Claude Code skills
  • Claude Code hooks
  • claude-mem
  • Claude Code best practices
  • Karpathy CLAUDE.md
  • AGENTS.md vs CLAUDE.md
  • Cursor rules
  • copilot-instructions.md
  • Why Claude ignores it
  • CLAUDE.md length
  • Committing CLAUDE.md
  • Agent memory compared
  • CLAUDE.md vs skills vs hooks
  • Make Claude Code remember
  • Review comments to rules
  • Slack decisions
  • Company brain
  • Context engineering, Claude 5
  • Context rot, stale rules
  • AGENTS.md research
  • ADRs for agents
  • Memory poisoning
  • Best MCP memory servers
  • Shared memory across agents
  • Growing CLAUDE.md
  • Which rules got used

Blog

Claude Code hooks: a practical guide with examples

Published: November 8, 2026

Claude Code hooks are commands that run at fixed points in a session: before a tool runs, after a file is edited, when a prompt is submitted, when Claude stops. Unlike a line in CLAUDE.md, a hook is not advice. It always runs, and a PreToolUse hook can block the action outright by exiting with code 2.

Checked against the hooks guide and reference on 2026-10-05 (Claude Code 2.1.289). The event list has grown to 33; the handful below are the ones most teams use. Three things changed in the fortnight before that date: sessions now start in auto mode, mods arrived and can overrule a hook, and Cursor runs Claude Code's hooks too. Each has a section below. The format-on-edit and Stop hook examples were checked on 2026-11-08.

What are Claude Code hooks?

Anthropic's best practices put it in one line: unlike CLAUDE.md instructions, which are advisory, hooks are deterministic and guarantee the action happens. A rule asks the model. A hook does not ask anyone. That is why the answer to “Claude keeps ignoring my rule” is so often “make it a hook”, and why the causes behind the ignoring, covered in why Claude ignores CLAUDE.md, do not apply to one. Where a hook sits beside the other mechanisms is in CLAUDE.md vs skills vs hooks.

Which hook events should you know?

EventFiresTypical use
PreToolUseBefore a tool call runs. Can block itRefuse dangerous commands, protect files
PostToolUseAfter a tool call succeedsFormat or lint the file that was just edited
UserPromptSubmitWhen you submit a prompt, before Claude sees itAdd context to the prompt, such as branch state
SessionStartWhen a session begins, resumes, clears or compactsLoad context the session should start with
StopWhen Claude finishes respondingRun the tests and refuse to stop until they pass
PreCompact / PostCompactAround context compactionRe-inject what must survive a summary
SessionEndWhen the session terminatesWrite logs, clean up
NotificationWhen Claude Code sends a notificationDesktop alert when Claude is waiting for you

How do you configure a hook?

Hooks live in a settings file under a hooks key. Each event takes matchers (a tool name or a regex such as Edit|Write) and the commands to run. This formats every file Claude edits:

// .claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Where the block goes decides who it applies to:

LocationScopeShared
~/.claude/settings.jsonAll your projectsNo
.claude/settings.jsonThis projectYes, commit it
.claude/settings.local.jsonThis projectNo, gitignored
Managed policy settingsThe whole organizationAdmin controlled
Plugin, skill or subagentWhile that is enabled or runningYes, ships with it

Run /hooks inside Claude Code to see everything configured. Since 2.1.286 it opens on one list grouped by event, each hook labelled with where it comes from; select one to see the full command and the file that defines it. The menu is read-only, so to change a hook you edit that file.

How do you block a command with a PreToolUse hook?

The hook receives the tool call as JSON on stdin. Exit 0 lets it through. Exit 2 blocks it, and what you write to stderr goes back to Claude as the reason, so it can change course. This is the pattern from Anthropic's guide, protecting files:

#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Register it on PreToolUse with the matcher Edit|Write, make it executable, and ask Claude to edit .env to test it. For structured control, exit 0 and print JSON with permissionDecision set to deny, ask or allow. When several hooks answer, the most restrictive wins.

A hook and your permission rules stack. A hook that exits 2 blocks the call even when an allow rule would let it through, and a deny rule still blocks a call the hook allowed. Since 2.1.288, if Claude Code cannot match a PreToolUse hook or cannot serialize the tool's input for it, the call is blocked rather than waved through. That fix is narrow: a guard that exits 1, or times out, still lets the call run, so a guard should exit 2 on anything it does not understand.

Do hooks still run in auto mode?

Yes, and they matter more there. Since 2.1.284, published 2026-09-28, interactive sessions start in auto mode when no permission mode is configured, on every plan and provider. Most tool calls are then approved by a classifier instead of by you. PreToolUse hooks still run before that: a hook that denies, or exits 2, blocks the call, and a hook that returns ask forces a prompt the classifier cannot approve on its own.

Auto mode adds one event of its own. PermissionDenied fires when the classifier refuses a call, which is the place to log what it stopped. And a PostToolUse hook can return classifierContext, a note the classifier reads before it judges the next action, since it never sees tool results themselves. Details are in the permission modes docs, and our note on auto mode vs --dangerously-skip-permissions covers which of the two keeps your hooks in charge.

Mods vs hooks: can a mod overrule a hook?

Yes. Mods arrived in 2.1.287 on 2026-10-01. A mod is a plugin whose handlers are JavaScript or TypeScript functions running inside Claude Code rather than shell commands, so it can draw panes, add commands and step into a tool call. The mods docs now call the hooks on this page “settings hooks” to tell them apart.

The part that matters for a guard: per the permissions docs, a mod that handles tool.check answers after your rules and your PreToolUse hooks, and its answer can replace theirs.

Your controlCan an installed mod approve past it?
A PreToolUse hook that blocksYes, unless the hook is in managed settings
An ask ruleYes
The auto mode classifierYes. A call the mod approves is not checked
A deny ruleNot on a machine with managed settings or a Team or Enterprise sign-in, by default. Anywhere else, yes

So a guard the whole team relies on belongs in managed settings, and a mod deserves the review you would give any dependency that runs with your permissions. If a shell script does the job, a settings hook is still the simpler choice.

Do Codex and Cursor have hooks too?

Both do, and both borrowed Claude Code's shape, so one guard script can serve all three.

CursorOpenAI Codex
Where.cursor/hooks.json, ~/.cursor/hooks.json, and enterprise paths.codex/hooks.json or [hooks] in .codex/config.toml, and the same under ~/.codex
Reads Claude Code's hooksYes, by default, from .claude/settings.json, .claude/settings.local.json and ~/.claude/settings.jsonNo
BlockingExit 2, or Claude Code's permissionDecision JSON. Any other non-zero exit lets the action throughExit 2, or permissionDecision: "deny". Matching hooks start at once, so one cannot stop another
Before a hook runsNothing extraYou review and trust each hook in /hooks; a changed hook is skipped until trusted again. Project hooks load only in trusted projects

Cursor maps the names for you: Bash becomes Shell, Edit becomes Write, and UserPromptSubmit becomes beforeSubmitPrompt. It does not map Notification or PermissionRequest. Codex calls a file edit apply_patch, so an Edit|Write matcher needs that name added there. Because Cursor fails open on a crash, write a guard to exit 2 when it cannot parse its own input. Sources: Cursor third-party hooks, Codex hooks.

Claude Code hook examples: format on edit, test before stop

Two hooks most teams add after the first guard. The first formats every file Claude writes or edits. PostToolUse receives the tool's input as JSON on stdin, and for Edit and Write the path in tool_input.file_path is always absolute:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

The second is a Stop hook, which runs when Claude finishes responding. Exiting 2, or returning {"decision": "block", "reason": "..."}, keeps Claude working and hands it the reason. This one refuses to let Claude stop while the tests fail:

#!/bin/sh
# .claude/hooks/tests-before-stop.sh, registered under "Stop"
input=$(cat)
# already continuing because of this hook: let it stop, so a test
# that cannot pass does not hold the session
[ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ] && exit 0
if ! out=$(npm test --silent 2>&1); then
  echo "Tests fail. Fix them before you finish:" >&2
  echo "$out" | tail -n 30 >&2
  exit 2
fi
exit 0

The stop_hook_active check gives Claude one more try. Drop it and Claude keeps going until the tests pass, up to Claude Code's cap of eight continuations in a row (set CLAUDE_CODE_STOP_HOOK_BLOCK_CAP to change it). For guidance rather than a block, return hookSpecificOutput.additionalContext instead: the conversation continues, shown as hook feedback, not an error. And for a one-off condition, the built-in /goal command is a Stop hook you do not have to write.

Which commands are worth blocking?

The ones your agents actually run, not the ones that sound scariest. A guard written from imagination can sit for months without matching anything. The costly commands are often ordinary: rm -r on a source directory, git checkout -- <paths> or git reset --hard throwing away uncommitted work. Read your agents' command history before you decide which guard comes first.

Start a new guard in log-only mode for a week, count what it would have blocked, then switch it to blocking. A guard that fires on legitimate work gets disabled by the second person it annoys.

Can a hook add context for Claude?

Yes. On UserPromptSubmit, return JSON with hookSpecificOutput.additionalContext and the text is added to Claude's context for that prompt. The guide's example adds the branch and a deploy freeze. Put the field inside hookSpecificOutput; at the top level it is silently ignored. SessionStart with the compact matcher is the documented way to re-inject what must survive a compaction.

This is the part of hooks that is easiest to overdo. Every line a hook injects is in the prompt, with the same cost and the same context rot as a line in CLAUDE.md. Inject what applies to this prompt, not everything that might.

How do you debug a hook that is not running?

Press Ctrl+O for the transcript view. A successful hook shows nothing; a block shows its reason; a failing hook shows a hook error notice. For the full picture, start with claude --debug-file /tmp/claude.log and tail the log, or run /debug mid-session. The usual culprits: the script is not executable, jq is missing, the matcher does not match the tool name, or a project setting set disableAllHooks.

Where Harbor fits

Harbor is itself delivered through hooks. harbor init installs hooks in Claude Code, Codex, Cursor and Copilot, and they serve each session the team's facts that apply to the repo and the task, so the agent starts with the decision from last month's review instead of rediscovering it. What a session learns is written back through the gate you set: on the default, a routine rule goes live on trial and anything risky waits for a person.

Harbor's guardrails run on these same hooks, in Claude Code, Codex, Cursor and Copilot, and since 2026-10-04 they cover MCP tool calls as well as shell commands, so the GitHub MCP server's merge_pull_request can be stopped the way gh pr merge is. The ready-made ones are off, and record nothing, until your team turns one on. A guardrail you write starts in observe, the log-only week above, and one you add from the library blocks from the start. Either can be switched, and Claude Code tells each developer when one is turned on. harbor off pauses the whole thing for one repo, and harbor doctor checks that the hooks are installed and reaching the agents you think they reach.

See what is installed where. Agents and MCP lists every hook event Harbor uses per agent, and the quickstart has the steps: sign up, name your workspace, then run npx harborloop init.

Questions

How do I make Claude Code run tests before it stops?

Add a Stop hook that runs the tests and exits 2 with the failure on stderr, or returns decision block with a reason. Claude keeps working and sees the reason. Check stop_hook_active in the hook's input so a test that cannot pass does not hold the session; Claude Code also stops after eight continuations in a row.

What are hooks in Claude Code?

Hooks are commands Claude Code runs at fixed points in a session, such as before a tool call, after a file edit, or when Claude stops. Unlike CLAUDE.md instructions, they always run.

How do I block a command in Claude Code?

Add a PreToolUse hook with a matcher such as Bash or Edit|Write. The script reads the tool call as JSON on stdin and exits with code 2 to block it; what it writes to stderr is passed back to Claude as the reason.

Where are Claude Code hooks configured?

In a settings file under a hooks key: ~/.claude/settings.json for all your projects, .claude/settings.json to share with the team, .claude/settings.local.json for yourself, or managed settings for the organization. Run /hooks to see them all.

Do hooks run in Claude Code auto mode?

Yes. PreToolUse hooks run before the auto mode classifier: a hook that denies or exits 2 blocks the call, and one that returns ask forces a prompt the classifier cannot approve on its own. Since 2.1.284, sessions start in auto mode when no permission mode is configured.

Does Cursor run Claude Code hooks?

Yes, by default. Cursor loads hooks from .claude/settings.json, .claude/settings.local.json and ~/.claude/settings.json, maps Bash to Shell and Edit to Write, and honours exit code 2 and Claude Code's permissionDecision JSON. Codex does not read them; it uses .codex/hooks.json.

Your team’s rules, in every agent, checked on every PR.

Harbor is shared memory for your team’s Claude Code, Cursor, Codex and Copilot. Free for you and one teammate. No card. No end date.

Start free →

Product

  • How it works
  • Every agent
  • Code review
  • The result
  • Security
  • For coding agents
  • Pricing

Developers

  • Docs
  • Quickstart
  • CLI
  • MCP server
  • Served and cited
  • Rule-file checker
  • Blog

Guides

  • Why Claude ignores CLAUDE.md
  • AGENTS.md vs CLAUDE.md
  • Does Claude Code read AGENTS.md?
  • Why Codex ignores AGENTS.md
  • Why Cursor ignores rules
  • Copilot instructions vs AGENTS.md
  • Shared memory across agents
  • Make Claude Code remember

Compare

  • vs Mem0
  • vs Zep
  • vs Unblocked
  • vs ByteRover
  • vs Cognee
  • vs Hindsight
  • vs OpenViking
  • vs Supermemory
  • vs Glen

Company

  • Questions
  • Contact
  • X
  • LinkedIn
  • Privacy Policy
  • Terms of Service
  • Data Processing Agreement
  • Refund Policy
© 2026 HarborStart free