Developer Tools AI Engineering 294 vues

Stop Letting Claude Code Run Wild—Lock It Down With Hooks

B
Bright Coding
Auteur
Stop Letting Claude Code Run Wild—Lock It Down With Hooks

What happens when your AI assistant decides to rm -rf / because you typo'd a prompt? Or when it silently overwrites your .env file with fake credentials? If you're using Claude Code without hooks, you're flying blind—trusting a probabilistic language model with deterministic consequences. One wrong tool call, one misinterpreted instruction, and your production database could vanish into digital dust.

Here's the brutal truth: Claude Code hooks are the most underutilized safety mechanism in modern AI-assisted development. While developers obsess over prompt engineering and model selection, they're ignoring the infrastructure layer that actually controls behavior. The claude-code-hooks-mastery repository by Dan Disler exposes a complete battle-tested system for deterministic control over Claude Code—transforming it from an unpredictable copilot into a rigorously governed engineering tool.

This isn't theoretical. The repository implements all 13 hook lifecycle events with production-grade security filtering, intelligent TTS feedback, team-based agent orchestration, and automated code quality validation. If you're serious about agentic coding at scale, you cannot afford to skip this. Let's dive into the architecture that top engineers are quietly deploying.

What Is claude-code-hooks-mastery?

The claude-code-hooks-mastery repository is a comprehensive reference implementation for Anthropic's Claude Code Hooks system. Created by Dan Disler (IndyDevDan), this open-source project demonstrates how to intercept, modify, block, and enhance every significant event in Claude Code's execution lifecycle.

Unlike basic hook examples that log a message and exit, this repository delivers production-hardened patterns for real engineering teams. It covers prompt validation, security blocking, sub-agent orchestration, team-based validation workflows, custom status lines, and output style transformations. The project leverages Astral's UV package manager for lightning-fast single-file script execution, ensuring hooks remain isolated from your main project dependencies.

Why is this trending now? Three converging forces: enterprise adoption of AI coding tools demanding audit trails and compliance; the rise of agentic workflows requiring deterministic control over autonomous agents; and increasing awareness that raw LLM outputs are fundamentally unsafe for unsupervised production work. This repository arrives as the definitive answer to "how do we actually control this thing?"

The repository doesn't just document hooks—it provides executable, tested implementations with 11 of 13 hooks validated through automated testing. Each hook includes JSON logging, error handling, and integration points for external services like ElevenLabs TTS, OpenAI, and Firecrawl MCP servers.

Key Features That Separate Amateurs From Pros

Complete Lifecycle Coverage

The repository implements all 13 Claude Code hook events: UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Notification, Stop, SubagentStart, SubagentStop, PreCompact, SessionStart, SessionEnd, PermissionRequest, and Setup. Most developers implement two or three hooks and call it done. This coverage ensures no critical execution path escapes monitoring or control.

Deterministic Security Layer

The PreToolUse hook doesn't just log—it blocks dangerous commands before execution. The repository pattern-matches against rm -rf variants, sudo rm, chmod 777, and system directory writes. Combined with PermissionRequest auditing and auto-allow for read-only operations, this creates defense-in-depth that no prompt engineering can replicate.

Intelligent Feedback Systems

Hooks trigger contextual audio feedback via a priority-ranked TTS system (ElevenLabs → OpenAI → pyttsx3). The Notification hook announces when your agent needs input. The Stop hook generates AI-powered completion summaries. The SubagentStop hook confirms task completion. You're never wondering what your autonomous agents are doing.

UV Single-File Script Architecture

Each hook lives as a standalone Python↗ Bright Coding Blog script with embedded dependency declarations. UV resolves and caches dependencies automatically—no virtual environment management, no dependency conflicts with your main project. This architectural decision makes hooks portable, fast, and maintainable across dozens of repositories.

Team-Based Agent Orchestration

The repository demonstrates builder/validator agent pairs coordinated through Claude Code's task system. Builders implement with full tool access; validators verify with read-only restrictions. PostToolUse validators automatically enforce Ruff linting and type checking. This isn't demo code—it's a scalable pattern for engineering organizations.

Custom Status Lines & Output Styles

Nine progressive status line implementations evolve from basic git info to token usage tracking with cache statistics. Eight output style transformations restructure Claude's responses for specific contexts—HTML generation, YAML configuration, ultra-concise mode, and TTS-optimized summaries.

Use Cases Where This Repository Shines

1. Production Safety Guardrails

Deploy Claude Code in CI/CD pipelines or production environments without sleepless nights. The PreToolUse hook blocks destructive operations; UserPromptSubmit validates against dangerous patterns; PermissionRequest creates audit trails for compliance. Financial services and healthcare organizations can finally adopt AI coding tools with regulatory confidence.

2. Autonomous Agent Swarms

Build complex multi-agent workflows where sub-agents handle specialized tasks—cryptocurrency analysis, code review, testing, documentation—while hooks ensure they stay in their lanes. The SubagentStart and SubagentStop hooks provide lifecycle management, and the meta-agent generates new specialized agents on demand.

3. Developer Experience Enhancement

Never lose context across sessions. The SessionStart hook loads git status, recent issues, and project context automatically. The PreCompact hook backs up transcripts before context window compression. Custom status lines display real-time token usage and session duration. Your AI assistant feels like a native extension of your development environment.

4. Quality-Assured Code Generation

Eliminate "vibe coding" disasters. The builder/validator pattern with automatic Ruff and type checking ensures generated code meets standards before you review it. The /plan_w_team command generates structured implementation plans with explicit validation criteria—not vague task lists.

Step-by-Step Installation & Setup Guide

Prerequisites

You'll need two core tools installed:

# Install Astral UV (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install Claude Code (Anthropic's CLI)
npm install -g @anthropic-ai/claude-code

Optional integrations for full feature activation:

# ElevenLabs for premium TTS (sign up at elevenlabs.io)
# OpenAI API key for GPT-powered summaries
# Ollama for local LLM fallback (ollama.com)
# Firecrawl MCP server for web scraping capabilities

Repository Setup

# Clone the mastery repository
git clone https://github.com/disler/claude-code-hooks-mastery.git
cd claude-code-hooks-mastery

# Verify UV installation and hook script execution
uv run .claude/hooks/user_prompt_submit.py --help

Configuration

Copy the hook configuration to your project:

# Option 1: Use directly from cloned repo
# Set environment variable for path resolution
export CLAUDE_PROJECT_DIR=$(pwd)

# Option 2: Integrate into your own project
cp -r .claude/ /path/to/your/project/

Configure .claude/settings.json with your hook paths:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run $CLAUDE_PROJECT_DIR/.claude/hooks/user_prompt_submit.py --log-only"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run $CLAUDE_PROJECT_DIR/.claude/hooks/pre_tool_use.py"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run $CLAUDE_PROJECT_DIR/.claude/hooks/stop.py"
          }
        ]
      }
    ]
  },
  "statusLine": {
    "type": "command",
    "command": "uv run $CLAUDE_PROJECT_DIR/.claude/status_lines/status_line_v3.py"
  }
}

Critical: Always use $CLAUDE_PROJECT_DIR prefix for reliable path resolution across working directories.

Environment Configuration

# Create environment file for persistent settings
echo "CLAUDE_ENV_FILE=.claude/env" >> .env

# Optional: Set engineer name for personalized TTS
export ENGINEER_NAME="Alex"

# Optional: Configure API keys for enhanced features
export OPENAI_API_KEY="sk-..."
export ELEVENLABS_API_KEY="..."

Verification

# Start Claude Code and submit any prompt
claude

# Check hook execution logs
ls logs/
cat logs/user_prompt_submit.json | jq '.'
cat logs/pre_tool_use.json | jq '.'

REAL Code Examples From the Repository

Example 1: Blocking Dangerous Commands (PreToolUse)

This is the hook that prevents catastrophic accidents. Extracted directly from the repository's security implementation:

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///

import sys
import re
import json

def is_dangerous_rm_command(command: str) -> bool:
    """Pattern match against known destructive rm variants."""
    dangerous_patterns = [
        r'rm\s+.*-[rf]',           # rm -rf variants
        r'sudo\s+rm',              # sudo rm commands
        r'chmod\s+777',            # Dangerous permissions
        r'>\s*/etc/',              # Writing to system directories
    ]
    
    for pattern in dangerous_patterns:
        if re.search(pattern, command, re.IGNORECASE):
            return True
    return False

def main():
    # Read hook payload from stdin (Claude Code passes JSON)
    payload = json.load(sys.stdin)
    
    tool_name = payload.get("tool_name", "")
    tool_input = payload.get("tool_input", {})
    
    # Extract command from Bash tool input
    if tool_name == "Bash":
        command = tool_input.get("command", "")
        
        # BLOCK dangerous commands with exit code 2
        if is_dangerous_rm_command(command):
            print(f"BLOCKED: Dangerous command detected: {command}", file=sys.stderr)
            sys.exit(2)  # Critical: exit code 2 blocks tool execution
    
    # Normal execution continues
    print(json.dumps({"continue": True}))
    sys.exit(0)

if __name__ == "__main__":
    main()

What's happening here: The hook receives JSON via stdin containing the tool name and parameters. For Bash commands, it regex-matches against destructive patterns. If matched, it prints to stderr and exits with code 2—this is the only mechanism that blocks tool execution in Claude Code. Exit code 2 feeds stderr directly back to Claude as an error message, preventing the tool from running.

Example 2: Prompt Validation with Context Injection (UserPromptSubmit)

The first line of defense—intercepts prompts before Claude processes them:

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = ["rich"]
# ///

import sys
import json
import os
from datetime import datetime, timezone

def main():
    # Parse the incoming prompt payload
    payload = json.load(sys.stdin)
    prompt = payload.get("prompt", "")
    session_id = payload.get("session_id", "")
    
    # Log for audit trail
    log_entry = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": session_id,
        "prompt": prompt
    }
    
    # Append to JSONL log file
    os.makedirs("logs", exist_ok=True)
    with open("logs/user_prompt_submit.json", "a") as f:
        f.write(json.dumps(log_entry) + "\n")
    
    # Security validation: block dangerous prompt patterns
    dangerous_prompts = [
        "curl .* | sh",           # Pipe to shell attacks
        "curl .* | bash",
        "wget .* | sh",
        "eval(",
    ]
    
    for pattern in dangerous_prompts:
        if pattern in prompt.lower():
            print(f"SECURITY: Blocked dangerous prompt pattern: {pattern}", file=sys.stderr)
            sys.exit(2)  # Blocks the entire prompt from reaching Claude
    
    # Context injection: add project context that Claude will see
    project_context = f"""
[Project Context]
Working Directory: {os.getcwd()}
Git Branch: {os.popen('git branch --show-current 2>/dev/null').read().strip() or 'N/A'}
Timestamp: {datetime.now(timezone.utc).isoformat()}
"""
    
    # Print to stdout: this text is PREPENDED to user's prompt for Claude to see
    print(project_context)
    sys.exit(0)

if __name__ == "__main__":
    main()

Critical insight: Anything printed to stdout in UserPromptSubmit becomes invisible context that Claude sees before your actual prompt. This enables dynamic context injection—git status, recent errors, project standards—without cluttering your prompt history. The --log-only, --validate, and --context flags mentioned in the repository control which behaviors activate.

Example 3: AI-Generated Completion Messages (Stop Hook)

Transform Claude's silent stops into informative summaries with TTS feedback:

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = ["openai", "anthropic", "ollama"]
# ///

import sys
import json
import os

def get_llm_summary(transcript: str) -> str:
    """Generate task completion summary using available LLM providers."""
    
    # Priority: OpenAI > Anthropic > Ollama > fallback
    if os.getenv("OPENAI_API_KEY"):
        from openai import OpenAI
        client = OpenAI()
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "system",
                "content": "Summarize the completed task in one sentence."
            }, {
                "role": "user", 
                "content": f"Transcript: {transcript[:4000]}"
            }]
        )
        return response.choices[0].message.content
    
    elif os.getenv("ANTHROPIC_API_KEY"):
        from anthropic import Anthropic
        client = Anthropic()
        response = client.messages.create(
            model="claude-3-haiku-20240307",
            max_tokens=100,
            messages=[{
                "role": "user",
                "content": f"Summarize this completed task in one sentence: {transcript[:4000]}"
            }]
        )
        return response.content[0].text
    
    # Fallback to local Ollama
    try:
        import ollama
        response = ollama.chat(
            model="llama3.2",
            messages=[{
                "role": "user",
                "content": f"Summarize: {transcript[:4000]}"
            }]
        )
        return response['message']['content']
    except:
        return "Task completed."

def main():
    payload = json.load(sys.stdin)
    
    # Prevent infinite loops: check if stop hook is already active
    if payload.get("stop_hook_active", False):
        sys.exit(0)
    
    # Read recent transcript for context
    transcript_path = payload.get("transcript_path", "")
    transcript = ""
    if transcript_path and os.path.exists(transcript_path):
        with open(transcript_path, 'r') as f:
            transcript = f.read()
    
    # Generate AI summary of completed work
    summary = get_llm_summary(transcript)
    
    # Output structured response for Claude Code
    output = {
        "continue": True,
        "stopReason": summary  # Shown to user in transcript mode
    }
    print(json.dumps(output))
    
    # Trigger TTS announcement (via utility script)
    os.system(f"uv run .claude/hooks/utils/tts/tts_queue.py '{summary}'")
    
    sys.exit(0)

if __name__ == "__main__":
    main()

Why this matters: The Stop hook receives the full session context and can block Claude from stopping if work is incomplete (exit with {"decision": "block", "reason": "..."}). Here it generates intelligent summaries using a cascading LLM priority system, then queues TTS playback. The stop_hook_active check prevents infinite loops—a critical safety pattern when blocking stops.

Example 4: Team-Based Validation with PostToolUse

Automatically enforce code quality after every file write:

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = ["ruff"]
# ///

import sys
import json
import subprocess
import os

def main():
    payload = json.load(sys.stdin)
    
    tool_name = payload.get("tool_name", "")
    tool_input = payload.get("tool_input", {})
    tool_response = payload.get("tool_response", {})
    
    # Only validate Python files after Write or Edit operations
    if tool_name in ("Write", "Edit"):
        file_path = tool_input.get("file_path", "")
        
        if file_path.endswith(".py") and os.path.exists(file_path):
            # Run Ruff linter
            result = subprocess.run(
                ["ruff", "check", file_path],
                capture_output=True,
                text=True
            )
            
            if result.returncode != 0:
                # BLOCK: Return structured error to Claude
                output = {
                    "decision": "block",
                    "reason": f"Ruff linting failed for {file_path}:\n{result.stdout}\nPlease fix before completing."
                }
                print(json.dumps(output))
                sys.exit(0)
    
    # Success path
    print(json.dumps({"continue": True}))
    sys.exit(0)

if __name__ == "__main__":
    main()

The builder/validator pattern in action: This hook fires after tool execution completes, so it cannot prevent the write—but it can force Claude to fix errors before considering the task complete. Combined with the Stop hook blocking completion, this creates an automatic correction loop: write → lint fail → block stop → Claude fixes → repeat until clean.

Advanced Usage & Best Practices

Hook Composition Strategy

Layer hooks for defense in depth: UserPromptSubmit for early validation, PreToolUse for execution prevention, PostToolUse for result verification, and Stop for completion gating. Never rely on a single hook type for critical safety.

Performance Optimization

Hooks execute with a 60-second timeout and run in parallel. Keep hooks stateless and fast. Use UV's dependency caching aggressively. For heavy operations like LLM calls, use async queues (see tts_queue.py) to prevent blocking.

Environment Persistence

The Setup hook with CLAUDE_ENV_FILE enables cross-session state. Store project-specific configurations, API keys, and context templates here—not in your shell profile where they leak across projects.

Meta-Agent Velocity

Don't hand-craft every agent. Use the meta-agent to generate properly structured sub-agents from descriptions. The compound effect: one meta-agent creates ten specialized agents, each handling tasks that previously required manual prompt engineering.

Status Line Evolution

Start with status_line_v3.py for agent session tracking, then upgrade to v8.py for token/cache analytics when optimizing context window usage. The progression teaches you what metrics actually matter for your workflow.

Comparison With Alternatives

Feature claude-code-hooks-mastery Basic Hook Examples Custom Claude Code Wrapper LangChain/LangGraph
Hook Coverage All 13 hooks 2-3 common hooks N/A (external tool) Different abstraction
Security Blocking Pre-execution with regex + exit codes Basic logging only Post-hoc analysis Agent-level, not tool-level
TTS Integration Multi-provider with priority fallback None Manual implementation Not built-in
Agent Orchestration Native task system + builder/validator None Complex custom code Heavy framework overhead
Code Quality Automatic Ruff/Ty validation None Manual CI integration Separate pipeline
Setup Complexity UV single-file scripts Varies High Very high
Learning Curve Documented, tested examples Fragmented Steep Framework-specific
Maintenance Active community, video docs Stale repos Self-maintained Framework-dependent

The decisive advantage: This repository operates at the native Claude Code hook layer, not as an external wrapper. You get deterministic control without sacrificing Claude Code's integrated context awareness, tool access, and model optimization. Wrappers lose the tight integration; frameworks add abstraction overhead you don't need.

FAQ

Q: Do hooks slow down Claude Code significantly? A: Hooks execute in parallel with a 60-second timeout. UV-resolved single-file scripts typically complete in under 500ms. The security and quality gains far outweigh negligible latency.

Q: Can hooks completely prevent prompt injection attacks? A: Hooks add deterministic filtering layers, but they're not foolproof against sophisticated adversarial inputs. Combine with PermissionRequest auditing and principle of least privilege for defense in depth.

Q: What's the difference between exit code 2 and JSON {"decision": "block"}? A: Exit code 2 is the universal blocking mechanism—simple, reliable, works across all hook types that support blocking. JSON decision fields provide richer context but have hook-specific behaviors. Use exit code 2 for critical blocks, JSON for nuanced control.

Q: How do I debug hooks that aren't firing? A: Check .claude/settings.json path resolution (use $CLAUDE_PROJECT_DIR), verify UV executable availability, and inspect logs/ directory for execution traces. Run hooks manually with echo '{}' | uv run hook.py to test.

Q: Can I use these hooks with Claude Desktop or API? A: These hooks are Claude Code CLI-specific. The architecture and concepts transfer, but implementation details differ. Anthropic's documentation covers API hooks separately.

Q: What's the recommended status line for beginners? A: Start with status_line_v3.py for agent session context, then experiment. v6 (context window bar) and v8 (token/cache stats) become valuable once you're optimizing long conversations.

Q: How does the builder/validator pattern handle conflicts? A: The task system coordinates through explicit dependencies. Validators don't override builders—they block completion until criteria are met. The primary agent mediates, creating a structured correction loop rather than chaotic agent arguments.

Conclusion

The claude-code-hooks-mastery repository isn't a toy demo—it's production infrastructure for the agentic coding era. Dan Disler has exposed the complete control surface that most developers ignore, wrapping it in tested, documented, extensible patterns that scale from personal projects to engineering organizations.

If you're using Claude Code without hooks, you're accepting probabilistic risk where deterministic safety is available. The 13-hook lifecycle, UV architecture, team-based validation, and intelligent feedback systems in this repository represent the current state of the art in AI-assisted development control.

My recommendation? Clone it today. Run the hooks. Watch your logs populate. Feel the difference when a dangerous command gets blocked before execution, when your status line shows real-time token usage, when your sub-agents coordinate through structured tasks instead of chaotic prompts. This is how professional agentic engineering works.

Stop hoping your AI behaves. Start commanding it deterministically.

👉 Get the complete implementation now: https://github.com/disler/claude-code-hooks-mastery

Follow IndyDevDan on YouTube for tactical agentic coding patterns, and explore Tactical Agentic Coding to prepare for the future of software engineering.

Commentaires 0

Aucun commentaire pour l'instant. Soyez le premier à réagir !

Laisser un commentaire