Guide
Claude Code hooks
Harrowtech.ai · last checked 13 October 2026
Claude Code hooks are commands that run automatically at set points in a session, such as before a file edit or when Claude needs your attention. This page explains what they are, where to set them up, and walks through one safe example, tested on Claude Code 2.1.296 on 13 October 2026.
Harrowtech.ai is independent of Anthropic.
What a hook is
Anthropic describes hooks as user-defined shell commands that Claude Code runs at specific points in its lifecycle. The point of them is certainty: a certain action always happens, instead of relying on the model to choose to do it. Typical uses are formatting a file after every edit, blocking changes to protected files, and sending a desktop notification.
A hook is not the same as telling Claude a rule in CLAUDE.md. A rule is an instruction the model may or may not follow. A hook is a program Claude Code runs itself.
Events
Each hook is attached to an event. Anthropic's reference lists 33 of them. These are the ones you will meet first:
PreToolUse | Before a tool call runs. It can block the call. |
|---|---|
PostToolUse | After a tool call succeeds. |
UserPromptSubmit | When you submit a prompt, before Claude processes it. |
Notification | When Claude Code sends a notification, for example because it is waiting for input. |
Stop | When Claude finishes responding. |
SessionStart and SessionEnd | When a session begins or resumes, and when it ends. |
PreCompact | Before the conversation is summarised to free up context. |
Where hooks are configured
Hooks live in a hooks block inside a settings file. Where you put it sets who it applies to:
~/.claude/settings.json | All your projects. Not shared. |
|---|---|
.claude/settings.json | One project. Can be committed so the team shares it. |
.claude/settings.local.json | One project. Not committed. |
| Managed policy settings | Organisation-wide, set by an admin. |
Plugins, skill files and subagent files can also carry hooks. Entries from different levels merge rather than replace each other. Inside Claude Code, type /hooks to browse everything configured, grouped by event.
Most hooks have "type": "command", which runs a shell command. Anthropic lists four other types: http, mcp_tool, prompt and agent. The last is marked experimental.
How a hook talks back
Claude Code sends the event details to your command as JSON on standard input. Your command answers with its exit code:
- Exit 0 means success.
- Exit 2 is a blocking error. On events that can block, such as
PreToolUse, the action is stopped and whatever you wrote to stderr is given to Claude as the reason. - Any other code is a non-blocking error. The action carries on. Anthropic states this plainly: exit 1 does not block, so use exit 2 to enforce a policy.
A matcher narrows when a hook fires. For tool events it filters on the tool name, so "Edit|Write" fires only for those two tools. Matchers are case-sensitive.
A worked example: protect files from edits
This is Anthropic's own example. It stops Claude editing .env, package-lock.json or anything under .git/. First, save this script as .claude/hooks/protect-files.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
Make it executable with chmod +x .claude/hooks/protect-files.sh. Then register it in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
Test the script on its own first, as Anthropic's troubleshooting section suggests, by piping in sample JSON and checking the exit code:
echo '{"tool_name":"Write","tool_input":{"file_path":"/x/.env"}}' | .claude/hooks/protect-files.sh
echo $?
What happened in testing
The hook was set up in an empty test folder, and Claude was asked, in print mode, to create a .env file and a notes.txt file. Two results are worth knowing about.
- The test machine had no
jq. The script printedjq: command not foundand then exited 0, so on its own it would have let the edit through. Anthropic's troubleshooting list covers this error and says to installjqor use Python or Node for the JSON. A guard that fails open is easy to miss, so run the piped test above and confirm you get exit 2. - With the
jqline swapped for a one-line Python call that reads the same field, the hook worked. Claude's attempt to write.envfailed with the messageBlocked: ... matches protected pattern '.env', no file was created, andnotes.txtwas written normally.
The Python line, if you have no jq
Replace the FILE_PATH=$(echo "$INPUT" | jq ...) line in the script with this one. It reads tool_input.file_path from the JSON and prints an empty string when the field is missing, as the // empty part of the jq version does:
FILE_PATH=$(echo "$INPUT" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("file_path",""))')
Tested on 13 October 2026, on a machine without jq, using Python 3.12.3. With the line in place, piping in the .env sample above printed the Blocked message and returned exit code 2. The same input with /x/notes.txt as the path returned 0. The line depends on python3 being installed, so run both piped tests again on the machine where the hook will live.
Security
Anthropic's warning, in its words: "Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration."
Two more points from the same section:
- In an interactive session, Claude Code holds back hooks from every settings file until you accept the workspace trust dialog for the folder. In a
-por SDK session there is no dialog, so hooks committed in a repository's.claude/settings.jsonrun even in a folder you have never trusted. Before scriptingclaude -pover code you did not write, read its.claude/settings, use--bare, or turn hooks off for the run with--settings '{"disableAllHooks": true}'. - Anthropic's practices for writing hooks: validate and sanitise inputs, always quote shell variables (
"$VAR", not$VAR), block path traversal by checking for.., use absolute paths for scripts, and skip sensitive files such as.envand.git/.
What this guide does not cover
It does not go through all 33 events, the JSON each one receives, http, mcp_tool, prompt and agent hooks, or hooks inside skills and plugins. The hooks reference has those. It also does not cover Windows PowerShell hooks, which need a "shell": "powershell" setting.
Harrowtech.ai's AI Setup service covers coding tools set up with rules and guardrails around how a team's developers work. See services.
Sources
- Anthropic: Automate actions with hooks, read 13 October 2026.
- Anthropic: Hooks reference, read 13 October 2026. The event count, exit codes and security section come from here.
- Test run on Claude Code 2.1.296 on 13 October 2026, in a throwaway folder. The Python line was tested the same day by piping sample JSON into the script, with bash and Python 3.12.3.
Related: All Harrowtech.ai guides · Claude Code CLI and headless mode · Claude Code subagents explained · Claude Code best practices