The exact code from the AyyazTech tutorial "I Made Claude Code Run Custom Scripts on Every Edit (Hooks Tutorial 2026)".
📺 Watch the full tutorial on YouTube → [link will be added after video publishes]
This repo shows two production-ready Claude Code hooks shipped in about 23 lines of JSON:
- PostToolUse hook that auto-formats every file Claude edits using Prettier
- PreToolUse hook that blocks Claude from touching
.envfiles or any other secrets
.claude/
├── settings.json # Wires both hooks into Claude Code
└── hooks/
├── auto-format.sh # Runs prettier on every file Claude edits
└── protect-env.sh # Blocks any edit to .env* files
The rest of the project is a tiny Focus Timer landing page used purely as a target for the demos.
You'll need these on your machine:
- Claude Code (version 2.1+)
- bun (used by the auto-format hook for
bunx prettier) - jq (used by the hook scripts to parse Claude's JSON input)
Install with:
# Claude Code
curl -fsSL https://claude.ai/install.sh | bash
# bun
curl -fsSL https://bun.com/install | bash
# jq (already shipped on macOS Sequoia+; on Linux use your package manager)
# macOS: brew install jq
# Ubuntu: sudo apt install jqgit clone https://github.com/AyyazTech/claude-code-hooks-demo.git
cd claude-code-hooks-demo
# The hook scripts need executable permission
chmod +x .claude/hooks/auto-format.sh .claude/hooks/protect-env.sh
# Create your own .env.local so the protect-env hook has something to block
cp .env.local.example .env.localLaunch Claude Code in this folder, then run this prompt:
Open index.html and add a class "dark-mode" to the body element. That's it.
Watch the file. Claude makes the edit, and the hook fires automatically and formats the whole file with Prettier. You didn't ask for formatting. The hook did it.
Same Claude Code session, run this prompt:
Update .env.local — change DATABASE_URL to postgres://localhost:5432/focus_timer
The PreToolUse hook fires first. The script checks the file path, sees it matches .env*, exits with code 2, and Claude is told the operation was blocked. Your secrets stay safe even from your own AI.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-env.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-format.sh"
}
]
}
]
}
}#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -n "$FILE_PATH" ] && bunx prettier --write "$FILE_PATH"#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" == *.env* ]]; then
echo "Blocked: $FILE_PATH is a protected env file" >&2
exit 2
fi
exit 0Each hook in .claude/settings.json matches a tool name (Edit|Write here, but it can be any Claude Code tool). When that tool fires, the hook script runs. The script receives JSON on stdin describing the tool call, parses out the file path with jq, and decides what to do:
- Exit code 0 lets the action happen
- Exit code 2 blocks the action, and Claude sees the stderr message as feedback
Claude Code supports 29 lifecycle events and 5 handler types (command, http, mcp_tool, prompt, agent). This demo uses two of the events with the command handler type.
For the full reference, see the official hooks documentation.
Both hook scripts are deliberately tiny so you can adapt them.
- Change
bunx prettier --writeinauto-format.shto any formatter you prefer (eslint --fix,black,gofmt, anything) - Change the
*.env*pattern inprotect-env.shto block any path you care about (*/secrets/*,*.pem,prod-*.yml, etc.) - Change the
matcherinsettings.jsonfromEdit|WritetoBashto gate shell commands, or tomcp__.*to gate every MCP tool
- Hooks run with your shell permissions. Treat them like cron jobs. A typo in a Bash command can do real damage.
- If your matcher is wrong, the hook silently does nothing. Launch Claude Code with
--debugto see which hooks fired. - Every hook adds latency to every matching tool call. Keep them fast.
- Use
disableAllHooks: truein your settings as a kill-switch if anything misbehaves.
📺 Watch the full tutorial on YouTube → [link will be added after video publishes]
This is part of the AyyazTech Claude Code Customization Series. The other two pillars are covered here:
- CLAUDE.md Tutorial — Rules for Claude
- Claude Code Skills Tutorial — Capabilities for Claude
- This tutorial — Hooks — Automation around Claude
For the synthesis explainer covering when to use which, watch Claude Code's 3 Customization Pillars Explained.
I show you the best AI coding setup. New tutorials every week on Claude Code, Cursor, local LLMs, MCP servers, and the AI-powered developer workflow.
🔥 Subscribe → https://www.youtube.com/@AyyazTech?sub_confirmation=1
🌐 Website → ayyaztech.com
💻 GitHub → github.com/AyyazTech
MIT. Do whatever you want with the code. Attribution appreciated but not required.