Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Read by thought-leaders and decision-makers around the world. Phone Number: +1-650-246-9381 Email: pub@towardsai.net
228 Park Avenue South New York, NY 10003 United States
Website: Publisher: https://towardsai.net/#publisher Diversity Policy: https://towardsai.net/about Ethics Policy: https://towardsai.net/about Masthead: https://towardsai.net/about
Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Founders: Roberto Iriondo, , Job Title: Co-founder and Advisor Works for: Towards AI, Inc. Follow Roberto: X, LinkedIn, GitHub, Google Scholar, Towards AI Profile, Medium, ML@CMU, FreeCodeCamp, Crunchbase, Bloomberg, Roberto Iriondo, Generative AI Lab, Generative AI Lab VeloxTrend Ultrarix Capital Partners Denis Piffaretti, Job Title: Co-founder Works for: Towards AI, Inc. Louie Peters, Job Title: Co-founder Works for: Towards AI, Inc. Louis-François Bouchard, Job Title: Co-founder Works for: Towards AI, Inc. Cover:
Towards AI Cover
Logo:
Towards AI Logo
Areas Served: Worldwide Alternate Name: Towards AI, Inc. Alternate Name: Towards AI Co. Alternate Name: towards ai Alternate Name: towardsai Alternate Name: towards.ai Alternate Name: tai Alternate Name: toward ai Alternate Name: toward.ai Alternate Name: Towards AI, Inc. Alternate Name: towardsai.net Alternate Name: pub.towardsai.net
5 stars – based on 497 reviews

Frequently Used, Contextual References

TODO: Remember to copy unique IDs whenever it needs used. i.e., URL: 304b2e42315e

Resources

Free: 6-day Agentic AI Engineering Email Guide.
Learnings from Towards AI's hands-on work with real clients.
Claude Code Hooks — Part 2: A Recipe Book for Real Teams
Latest   Machine Learning

Claude Code Hooks — Part 2: A Recipe Book for Real Teams

Last Updated on May 27, 2026 by Editorial Team

Author(s): Narmadha

Originally published on Towards AI.

If you read Part 1, you know the why behind Claude Code Hooks: they turn “Claude will probably follow this rule” into “Claude has no choice but to follow this rule.”

Claude Code Hooks — Part 2: A Recipe Book for Real Teams

But the most common question after Part 1 was simple: “Okay — what do I actually write?”

This article answers that. But before the recipes, there are two things Part 1 skipped that you need to understand first — otherwise your scripts are just guessing.

The Complete Lifecycle: Every Trigger Point

Claude Code fires hooks at seven built-in events, grouped into three cadences:

Once per session

  • SessionStart — fires the moment you open Claude Code
  • SessionEnd — fires when the session closes

Once per turn

  • UserPromptSubmit — fires every time you hit Enter
  • Stop — fires when Claude signals it's finished
  • StopFailure — fires when Claude stops because something went wrong

Every tool call

  • PreToolUse — fires before Claude executes any tool
  • PostToolUse — fires after any tool completes

The three cadences matter because they serve different purposes. Session-level hooks are for setup and teardown. Turn-level hooks are for quality gates and prompt enhancement. Tool-level hooks — PreToolUse and PostToolUse — are where most of the real control lives, because they fire the most often and give you the finest granularity.

The Missing Piece: What Claude Code Actually Sends Your Script

Part 1 explained that Claude Code sends event data to your script via stdin as JSON. But it never showed what that JSON looks like — which means writing a hook is a guessing game without it.

Here’s the real payload for a PreToolUse event on a Bash command:

{
"session_id": "abc-123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm install express-validator"
}
}

And for a file write (Write tool):

{
"session_id": "abc-123",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "src/routes/users.ts",
"content": "..."
},
"tool_response": {
"success": true
}
}

Your script reads this from stdin and exits with a number:

  • exit 0 → allow the action to proceed
  • exit 2 → block it (your stderr message gets sent back to Claude as the reason)

There’s also a third option that Part 1 didn’t mention at all.

You can inject content back into Claude’s context via stdout.

If your script prints JSON to stdout on exit 0, Claude Code reads it and adds it to Claude's context window. This means hooks aren't just blockers — they're a way to silently feed Claude information at exactly the right moment.

{ "output": "Team rule: always use zod for validation, not joi." }

Return that from a UserPromptSubmit hook and Claude reads it before processing your prompt — without you typing a word.

With those foundations in place, here are six recipes.

Recipe 1 — Block Writes to Sensitive Files

The problem: Claude helpfully edits .env files, private keys, or credentials it should never touch.

The hook: PreToolUse on Write and Edit.

# .claude/scripts/block-sensitive-files.py
import json, sys, re
payload = json.load(sys.stdin)
file_path = payload.get("tool_input", {}).get("file_path", "")

BLOCKED = [r"\.env$", r"\.pem$", r"\.key$", r"secrets/", r"\.aws/credentials"]

for pattern in BLOCKED:
if re.search(pattern, file_path):
print(f"BLOCKED: writes to '{file_path}' are not allowed.", file=sys.stderr)
sys.exit(2)
sys.exit(0)
"PreToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "python .claude/scripts/block-sensitive-files.py"
}]
}]

Claude gets told exactly which file was blocked and why. It reroutes on its own — no intervention from you.

Recipe 2 — Auto Type-Check After Every File Write

The problem: Claude writes TypeScript that passes ESLint but breaks the compiler. You only find out after ten more file writes.

Learn about Medium’s values

The hook: PostToolUse on Write, running tsc --noEmit immediately after each file.

#!/bin/bash
# .claude/scripts/typecheck.sh
FILE=$(python3 -c "import sys,json; d=json.load(sys.stdin); print(d['tool_input'].get('file_path',''))")

if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
OUTPUT=$(npx tsc --noEmit 2>&1)
if [ $? -ne 0 ]; then
echo "$OUTPUT" >&2
echo "TypeScript errors found — fix before continuing." >&2
exit 2
fi
fi
exit 0
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "bash .claude/scripts/typecheck.sh"
}]
}]

Every .ts and .tsx file Claude writes gets type-checked immediately. No more discovering 15 compiler errors at the very end of a session.

Recipe 3 — Inject Team Context at Session Start

The problem: Every session, you want Claude to already know the current sprint goal, what files are off-limits, and which libraries your team prefers.

The hook: SessionStart with stdout injection — the output field gets added to Claude's context before you type your first prompt.

# .claude/scripts/inject-context.py
import json, sys
from datetime import date

context = {
"output": f"""
=== TEAM CONTEXT ({date.today()}) ===
Sprint goal: Payments refactor — do not touch unrelated files
Off-limits: src/legacy/billing.py (migration in progress)
Preferred libraries: zod (not joi), axios (not fetch), dayjs (not moment)
Test command: npm run test:unit
===
"""

}

print(json.dumps(context))
sys.exit(0)
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "python .claude/scripts/inject-context.py"
}]
}]

Claude is fully briefed before you type a word. No CLAUDE.md needed for team-wide rules that change sprint to sprint — just update this script.

Recipe 4 — Slack Notification When Claude Finishes

The problem: You kick off a long Claude Code session, walk away, and forget to check back.

The hook: Stop event posting to Slack's webhook.

# .claude/scripts/notify-slack.py
import json, sys, urllib.request, os

payload = json.load(sys.stdin)
session_id = payload.get("session_id", "unknown")

webhook_url = os.environ["SLACK_WEBHOOK_URL"]
message = {
"text": f" Claude Code session `{session_id}` has finished. Time to review."
}

req = urllib.request.Request(
webhook_url,
data=json.dumps(message).encode(),
headers={"Content-Type": "application/json"}
)
urllib.request.urlopen(req)
sys.exit(0)
"Stop": [{
"hooks": [{
"type": "command",
"command": "python .claude/scripts/notify-slack.py"
}]
}]

Swap the Slack webhook for any endpoint — Teams, Discord, PagerDuty, or your own internal system. The StopFailure event is also worth hooking here so you get notified differently when Claude stops because something went wrong.

Recipe 5 — Audit Log of Everything Claude Does

The problem: In a regulated environment or shared team project, you want a record of every tool Claude executed — what it tried, when, and whether it was allowed.

The hook: PostToolUse writing to a local .jsonl file.

# .claude/scripts/audit-log.py
import json, sys
from datetime import datetime, timezone

payload = json.load(sys.stdin)

log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"session_id": payload.get("session_id"),
"tool": payload.get("tool_name"),
"input": payload.get("tool_input"),
"success": payload.get("tool_response", {}).get("success")
}

with open(".claude/audit.jsonl", "a") as f:
f.write(json.dumps(log_entry) + "\n")
sys.exit(0)
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": "python .claude/scripts/audit-log.py"
}]
}]

The .jsonl format means every line is a valid JSON object — pipe it into jq, load it into a database, or pull it into a dashboard. Add .claude/audit.jsonl to .gitignore and you have a local, private audit trail that costs nothing.

Recipe 6 — Policy Enforcement Without Writing Code

All five recipes above require a script. But there’s a handler type that requires nothing except plain English: prompt.

You write a condition in natural language. Claude evaluates it against the tool call and returns either ALLOW or BLOCK with a reason.

"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "Does this Bash command delete files, drop databases, reset migrations, or modify system configuration outside the project directory? If yes, respond BLOCK with a one-sentence reason. If no, respond ALLOW."
}]
}]

No Python. No bash. The trade-off is latency — a prompt hook is slower than a script because it makes an LLM call. But for nuanced policies that are hard to express in code, it's the right tool.

One Security Note Worth Making

Project-level hooks in .claude/settings.json get committed to git and run automatically when any teammate opens Claude Code. That's the feature — but it's also the risk.

A hook script runs with your shell’s full permissions. A buggy script in a cloned repo’s .claude/ folder can cause real damage. A malicious one in a third-party repo can do worse.

Two habits that protect you:

  1. Never open Claude Code in an unfamiliar cloned repo without reviewing .claude/settings.json first.
  2. Code-review hook scripts the same way you’d review any shell script going into your CI pipeline. They deserve the same scrutiny.

Your Starter Project Layout

your-project/
├── .claude/
│ ├── settings.json ← hook configuration
│ ├── audit.jsonl ← add to .gitignore
│ └── scripts/
│ ├── block-sensitive-files.py
│ ├── typecheck.sh
│ ├── inject-context.py
│ ├── notify-slack.py
│ └── audit-log.py
├── CLAUDE.md
└── src/

Commit everything except audit.jsonl. Your whole team gets the hooks the moment they pull.

What’s Next

Part 3 will go deeper into the agent handler — the most powerful and least understood hook type. Instead of running a script, it spawns a subagent with access to tools like Read, Grep, and Glob to deeply inspect your codebase before allowing an action.

Think of it as a code reviewer that runs before Claude writes a single line. That’s worth its own article.

Did any of these recipes solve a problem you’ve been dealing with? Drop it in the comments — I’d like to know what the community is building.

Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

Published via Towards AI


Towards AI Academy

We Build Enterprise-Grade AI. We'll Teach You to Master It Too.

15 engineers. 100,000+ students. Towards AI Academy teaches what actually survives production.

Start free — no commitment:

6-Day Agentic AI Engineering Email Guide — one practical lesson per day

Agents Architecture Cheatsheet — 3 years of architecture decisions in 6 pages

Our courses:

AI Engineering Certification — 90+ lessons from project selection to deployed product. The most comprehensive practical LLM course out there.

Agent Engineering Course — Hands on with production agent architectures, memory, routing, and eval frameworks — built from real enterprise engagements.

AI for Work — Understand, evaluate, and apply AI for complex work tasks.

Note: Article content contains the views of the contributing authors and not Towards AI.