Skip to content

C2 · lesson 4 · 10 min read

Hooks

Run your own scripts at fixed points in Claude Code's lifecycle, for example to block a commit that contains secrets.

Ընտրեք տեքստը՝ հայերեն թարգմանությունը տեսնելու համար։Highlight text to see Armenian. Machine-assisted translation; English is the source of truth.

Official video by Anthropic on YouTube. Pressing play loads it from youtube-nocookie.com. ·open on YouTube

Permission rules decide whether a tool may run. Hooks let you run your own code at fixed points, for example before a tool call, after a file edit, or when a session starts. Use them for checks that can’t be written as a simple pattern: secret scanning, formatting after edits, or notifying a channel.

Events you will use most

Event Fires Typical use
PreToolUse Before a tool call; can block it Policy checks, secret scanning
PostToolUse After a tool call succeeds Run formatters or linters on edited files
UserPromptSubmit When you send a prompt Add context, reject prompts
SessionStart When a session starts or resumes Load environment info
Stop / SubagentStop When Claude or a subagent finishes Notify, or ask it to keep going
PreCompact Before context is compacted Save notes

There are many more (for example PermissionRequest, Notification, SessionEnd). The reference page has the full list.

Configuration shape

Hooks live in the same settings files as permissions:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git commit *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-secrets.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
  • matcher picks the tool. It can be an exact name, a | list such as Edit|Write, or a regex; * or an empty matcher matches everything.
  • if narrows the hook further with a permission-rule pattern.
  • ${CLAUDE_PROJECT_DIR} is the project root, so the path works from any subdirectory.
  • type can be command, http, mcp_tool, prompt or agent.

Input and exit codes

A command hook gets a JSON object on stdin. It includes session_id, cwd and hook_event_name, and for tool events also tool_name and tool_input. The hook answers through its exit code:

hook exits0 → success, stdout JSON applied2 → block; stderr goes to Claudeother → error shown, action continues
What the exit code of a PreToolUse hook means.

Exit code 2 is the one that blocks. The stderr text goes back to Claude as the reason, so write something actionable. Instead of an exit code, a hook can exit 0 and print JSON. For PreToolUse that JSON is a hookSpecificOutput with permissionDecision set to allow, deny, ask or defer and a permissionDecisionReason.

Example: block secrets in commits

#!/usr/bin/env bash
# PreToolUse hook: refuse git commits whose staged diff looks like it has secrets.
set -euo pipefail

input=$(cat)
cmd=$(jq -r '.tool_input.command // ""' <<<"$input")
[[ "$cmd" == git\ commit* ]] || exit 0

cd "$(jq -r '.cwd' <<<"$input")"
if git diff --cached -U0 | grep -E -q 'AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----'; then
  echo "Blocked: the staged diff contains what looks like an AWS key or private key. Remove it and use the secret store instead." >&2
  exit 2
fi
exit 0

Recap

  • Hooks run your code at lifecycle events. PreToolUse can block, and PostToolUse reacts to what happened.
  • Configure them in settings: an event, a matcher (plus an optional if), and a handler.
  • Exit 0 continues, exit 2 blocks with stderr as the reason, and any other code is a non-blocking error.
  • Treat hooks as code with your permissions, especially in CI.

See it in action

A PreToolUse hook in action
exit 2exit 0Claude proposesBash: git commitPreToolUse hookblock-secrets.shblockedstderr → Claudetool runsPostToolUseformatter

1/5 Claude decides to call a tool: here Bash running git commit.

all steps
  1. Claude decides to call a tool: here Bash running git commit.
  2. Before the call runs, every matching PreToolUse hook receives it as JSON on stdin (tool_name, tool_input, cwd…).
  3. Exit code 2 blocks the call. The hook's stderr goes back to Claude as the reason, so Claude can fix the problem.
  4. Exit 0 lets the call go ahead, still subject to the normal permission rules.
  5. After the tool succeeds, PostToolUse hooks run: a good place for formatters and linters.
a PreToolUse hook blocks a commit · illustrative session
transcript
> commit the new CI config
Bash(git commit -m "ci: add deploy job")
PreToolUse hook blocked the call (exit 2):
Blocked: the staged diff contains what looks like an AWS key or private key.
The staged ci.yml has an inline access key. I'll move it to a repository secret.
Edit(.github/workflows/ci.yml)  → uses ${{ secrets.AWS_ROLE_ARN }}
Bash(git commit -m "ci: add deploy job")  ✓

$ ./try-it.sh

Try it: block commits that contain secrets

  1. Save the script from this lesson as .claude/hooks/block-secrets.sh and make it executable.
  2. Register it under PreToolUse with matcher Bash and "if": "Bash(git commit *)" in .claude/settings.json.
  3. Stage a file containing AKIAIOSFODNN7EXAMPLE and ask Claude to commit it. Confirm that the hook blocks the commit and that Claude sees your reason.
  4. Replace the grep with gitleaks protect --staged if you have gitleaks installed.

Check yourself

4 questions. Original practice questions, not taken from the real exam.

  1. You want to stop Claude from committing staged files that contain private keys. Your script detects them reliably.

    1. Which event should run the script, and how does it block the commit?

  2. A PostToolUse hook should run terraform fmt only after Claude edits files, not after it reads them or runs commands.

    2. Which matcher fits?

  3. Your CI job runs claude -p against pull requests from external forks. A fork adds a .claude/settings.json with a PreToolUse hook that runs curl to an unknown host.

    3. Which two statements are true? (Choose two.)

    Choose 2.

  4. Instead of using exit codes, your PreToolUse hook exits 0 and prints JSON to send the call to a human for approval.

    4. Which output does that?

Answer every question to check.