CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-05-05Advanced

Building a Zero-Touch Code Review Environment with Claude Code Hooks

Learn how to use Claude Code's hook system to automatically review code on every tool execution. Covers PostToolUse, Stop hooks, and the pitfalls to avoid when implementing PreToolUse blockers.

Claude Code197Hooks6Automation39Code Review2Developer Tools

There's a quiet disappointment in opening a PR and seeing nothing but "LGTM" from a reviewer. In a busy development team, giving every commit the attention it deserves isn't always realistic. To close that gap, I built a setup using Claude Code's hook system that automatically reviews code as I work — no manual triggering required.

What surprised me most was that hooks aren't just a "run a command" feature. By combining PreToolUse, PostToolUse, and Stop events thoughtfully, you create an interactive quality layer that runs alongside Claude Code itself — distinct from CI/CD, and far more immediate.

Understanding When Hooks Fire

Hooks are defined in .claude/settings.json. There are four trigger points:

  • PreToolUse: Before a tool runs (returning exit 2 blocks the tool)
  • PostToolUse: After a tool completes (use this for analysis and reporting)
  • Notification: When Claude sends a notification
  • Stop: When the session ends

For automatic code review, PostToolUse on the Bash tool is the right choice. Every time Claude Code runs a shell command, the hook fires and checks whether any tracked files changed.

One thing to get right early: use matcher to target specific tools. Attaching heavy processes to every tool invocation makes Claude Code noticeably sluggish.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/auto-review.py"
          }
        ]
      }
    ]
  }
}

The Review Script

Claude Code passes context to hooks via stdin as JSON. Here's the script that reads that context, detects changed files, and asks Claude to review them:

#!/usr/bin/env python3
import json
import sys
import subprocess
 
def get_changed_files():
    result = subprocess.run(
        ["git", "diff", "--name-only", "HEAD"],
        capture_output=True, text=True
    )
    files = result.stdout.strip().split("\n")
    return [f for f in files if f.endswith((".ts", ".tsx", ".py", ".go"))]
 
def review_with_claude(code: str, file_path: str):
    prompt = f"""Review the following code briefly.
If there are issues, describe them. If everything looks fine, just respond with "OK".
 
File: {file_path}

{code[:2000]}

 
    result = subprocess.run(
        ["claude", "-p", prompt, "--output-format", "text"],
        capture_output=True, text=True, timeout=30
    )
    if result.returncode == 0:
        print(result.stdout)
 
def main():
    try:
        hook_input = json.loads(sys.stdin.read())
    except json.JSONDecodeError:
        return
 
    if hook_input.get("tool_name") != "Bash":
        return
 
    changed_files = get_changed_files()
    if not changed_files:
        return
 
    for file_path in changed_files[:3]:
        try:
            with open(file_path) as f:
                code = f.read()
        except (FileNotFoundError, PermissionError):
            continue
 
        print(f"\n📋 Auto-review: {file_path}")
        review_with_claude(code, file_path)
 
if __name__ == "__main__":
    main()

In practice, the review comments appear inline in the terminal as Claude Code works. It felt noisy at first, but after a few sessions it became useful — catching loose variable names and missing error handling that I would have deferred to "later."

Generating Session Reports with Stop Hooks

The Stop hook runs when a Claude Code session ends, which makes it ideal for summarizing what happened.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/session-report.py"
          }
        ]
      }
    ]
  }
}
#!/usr/bin/env python3
import subprocess
from datetime import datetime
from pathlib import Path
 
def get_recent_commits():
    result = subprocess.run(
        ["git", "log", "--since=6 hours ago", "--oneline", "--no-merges"],
        capture_output=True, text=True
    )
    return result.stdout.strip()
 
def generate_summary(commits: str) -> str:
    if not commits:
        return "No commits in this session."
 
    prompt = f"""Summarize the following git commits in 3 bullet points.
Use plain language that's easy to share in a standup.
 
Commits:
{commits}"""
 
    result = subprocess.run(
        ["claude", "-p", prompt, "--output-format", "text"],
        capture_output=True, text=True, timeout=30
    )
    return result.stdout if result.returncode == 0 else "Summary generation failed."
 
def main():
    commits = get_recent_commits()
    summary = generate_summary(commits)
 
    log_dir = Path.home() / "work-logs"
    log_dir.mkdir(exist_ok=True)
 
    today = datetime.now().strftime("%Y-%m-%d")
    log_file = log_dir / f"{today}.md"
 
    with open(log_file, "a") as f:
        timestamp = datetime.now().strftime("%H:%M")
        f.write(f"\n## {timestamp} Session ended\n\n{summary}\n")
 
    print(f"📝 Work log saved: {log_file}")
 
if __name__ == "__main__":
    main()

These logs become genuinely useful when writing weekly summaries or project status updates — things that normally require reconstructing history from memory.

The Gotcha with PreToolUse Blockers

PreToolUse hooks that return exit 2 can block tool execution entirely. This sounds powerful, but there's a failure mode worth knowing: if your blocker is too broad, Claude Code enters a loop where it cannot recover.

I learned this when I built a hook to block Bash execution whenever TypeScript had compilation errors. The problem: Claude needs to run Bash commands to fix those errors. Block the tool, block the fix, block the tool again.

The design principle that works: never block the tools Claude needs to self-correct. Use precise matcher patterns and limit blocking to genuinely dangerous commands:

#!/usr/bin/env python3
import json
import sys
import re
 
DANGEROUS_PATTERNS = [
    r"rm\s+-rf\s+/",
    r"DROP\s+TABLE",
]
 
def main():
    try:
        hook_input = json.loads(sys.stdin.read())
    except json.JSONDecodeError:
        sys.exit(0)
 
    command = hook_input.get("tool_input", {}).get("command", "")
 
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            print(f"Blocked dangerous command: {command[:100]}")
            sys.exit(2)
 
if __name__ == "__main__":
    main()

When exit 2 fires, Claude Code receives your stdout message and uses it to decide what to do next. A descriptive message helps Claude take a sensible alternative action rather than retrying the same blocked command.

Scope: User vs. Project

Hooks can live in two places:

  • ~/.claude/settings.json — applies to all projects
  • .claude/settings.json — applies to the current project only

The review and reporting hooks covered here are generic enough for the user scope. Project-specific blockers (say, one that enforces your team's linting rules) belong in the project scope so they don't interfere with other work.

Setup is minimal:

mkdir -p ~/.claude/hooks
chmod +x ~/.claude/hooks/auto-review.py
chmod +x ~/.claude/hooks/session-report.py

Start with the Stop hook and the session report. It has no risk — it only runs after Claude Code finishes — and you'll immediately have a habit of ending sessions with a written summary rather than a blank memory.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-06-14
A SubagentStop Hook That Grades Subagent Output and Sends It Back to Be Redone
When a Claude Code subagent occasionally returns rule-breaking work, a SubagentStop hook can grade it automatically and ask for a redo. Here is a working setup with code and field notes.
Claude Code2026-04-27
Claude Code Hooks: A Complete Field Guide to All 8 Hook Types and How to Pick the Right One
Claude Code hooks are powerful, but most people give up before figuring out which event does what. Here's the field guide I wish I had when I started — six months of running hooks in production, distilled.
Claude Code2026-04-02
Claude Code Hooks: Building Automated Workflows in Practice
A systematic guide to Claude Code Hooks. Learn how to use PreToolUse, PostToolUse, Stop, and Notification hooks to automate code quality checks, logging, notifications, and more.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →