Skip to content
zero2vibecodelearn vibe coding
Level 2 · Make it yours 7 min Reviewed by the author · Oct 15, 2026 Verified against Claude Code v2.1.292 · Oct 15, 2026Official docs: HooksSomething out of date? Tell us

Hooks: a rule that cannot be forgotten

A rule in CLAUDE.md is advice Claude can miss. A hook is your own command that Claude Code runs at a set moment, every time. A PreToolUse hook that exits with code 2 blocks the action before permissions are even checked.

Level 2 · 0 / 4 lessons0%
In this lesson

You write in CLAUDE.md: “never edit package-lock.json”. Claude follows it almost always. But “almost” is exactly the problem: a rule in memory is advice the model can miss in a long session.

A works differently. It is your own command that Claude Code runs at a set moment in the session, every time — whatever Claude decides.

Hook events

A hook is attached to an event. The main ones:

EventWhen it fires
SessionStartwhen the session starts
UserPromptSubmitwhen you send a request
PreToolUsebefore every tool call
PostToolUseafter a successful tool call
Stopwhen Claude finishes answering
Notificationwhen Claude Code needs your attention

Today we care about PreToolUse — the hook that can stop an action.

How PreToolUse blocks

Before a tool is called, Claude Code runs your script and feeds it JSON describing the event — which file is about to be edited, for example. The exit code decides what happens next:

  • 0 — all clear, the action goes ahead;
  • 2 — the call is blocked, and whatever the script printed to stderr goes to Claude as the reason.

Claude sees the explanation — say, “Blocked: package-lock.json is protected” — and changes its plan.

One important detail: PreToolUse fires before the permission mode is checked. So the block holds in every mode, Auto included.

Registering a hook

Hooks are declared in a settings file, next to the rules from the previous level:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-files.sh", "args": [] }
        ]
      }
    ]
  }
}
  • matcher picks the tools: Edit|Write means file edits and writes only;
  • ${CLAUDE_PROJECT_DIR} fills in the project path, so the hook is found from anywhere;
  • with "args": [] the script runs directly, without a shell, and the path does not need quoting.

Inside a session, /hooks shows which hooks were picked up.

You can test the script without Claude: feed it the JSON Claude Code would send and look at the exit code.

echo "{\"tool_input\":{\"file_path\":\"$PWD/package-lock.json\"}}" | .claude/hooks/protect-files.sh; echo "exit $?"

If you get exit 2, the hook will fire in a session too.

Quick check

A hook on Edit|Write protects .env. Claude ran the command echo X >> .env. Does the hook stop it?

Careful

Practice in the terminal

Check your understanding

Answered 0/3
Mode:

How is a hook different from a rule in CLAUDE.md?

How does a PreToolUse hook block an action?

Does a PreToolUse hook block an action in Auto mode?

Finish the task in the terminal and answer the questions to complete the lesson.

📝 My notes

Saved automatically in this browser · shows up on the Notes page.

Task

Protect files from edits: register a PreToolUse hook that fires on the Edit and Write tools and runs the script .claude/hooks/protect-files.sh.

Tab complete · ↑ history · Ctrl+L clear · help commands

  • Not done: The hook script is in .claude/hooks
  • Not done: The hook is registered on the PreToolUse event
  • Not done: The hook watches Edit and Write

Lesson Q&A

How is a hook different from a rule in CLAUDE.md?

CLAUDE.md is advice Claude can miss; a hook is a command Claude Code runs at a set moment every time, whatever Claude decides — A hook fires deterministically, on an event, without the model being involved. It is a rule that cannot be "forgotten".

How does a PreToolUse hook block an action?

It exits with code 2 — the tool call is cancelled, and whatever the hook printed to stderr goes to Claude as the reason — Exit code 2 from PreToolUse blocks the call, and Claude receives the stderr text as the explanation, so it can adjust its plan.

Does a PreToolUse hook block an action in Auto mode?

Yes — PreToolUse fires before the permission mode is checked, so the block holds in any mode — A PreToolUse hook runs before the mode check. That makes it more reliable than mode settings: Shift+Tab cannot get around it.

A hook on Edit|Write protects .env. Claude ran the command echo X >> .env. Does the hook stop it?

The Edit|Write matcher only catches the editing tools. Commands need a separate hook with a Bash matcher, or a Stop hook that checks the working tree.

Commands in this lesson

Terms in this lesson

Move between lessons with ← → · search with ⌘K

Written and reviewed by Mikhail Kuzmitskii