hooks-builder

A comprehensive guide for creating Claude Code hooks — event-driven automation that monitors and controls Claude's actions.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hooks-builder" with this command: npx skills add mike-coulbourn/claude-vibes/mike-coulbourn-claude-vibes-hooks-builder

Hooks Builder

A comprehensive guide for creating Claude Code hooks — event-driven automation that monitors and controls Claude's actions.

Quick Reference

The 10 Hook Events

Event When It Fires Can Block? Supports Matchers?

PreToolUse Before tool executes YES YES (tool names)

PermissionRequest Permission dialog shown YES YES (tool names)

PostToolUse After tool succeeds No YES (tool names)

Notification Claude sends notification No YES

UserPromptSubmit User submits prompt YES No

Stop Claude finishes responding Can force continue No

SubagentStop Subagent finishes Can force continue No

PreCompact Before context compaction No YES (manual/auto)

SessionStart Session begins No YES (startup/resume/clear/compact)

SessionEnd Session ends No No

Exit Code Semantics

Exit Code Meaning Effect

0 Success stdout parsed as JSON for control

2 Blocking error VETO — stderr shown to Claude

Other Non-blocking error stderr logged in debug mode

Configuration Locations

~/.claude/settings.json → Personal hooks (all projects) .claude/settings.json → Project hooks (team, committed) .claude/settings.local.json → Local overrides (not committed)

Essential Environment Variables

Variable Description

$CLAUDE_PROJECT_DIR

Project root directory

$CLAUDE_CODE_REMOTE

Remote/local indicator

$CLAUDE_ENV_FILE

Environment persistence path (SessionStart)

$CLAUDE_PLUGIN_ROOT

Plugin directory (plugin hooks)

Key Commands

/hooks # View active hooks claude --debug # Enable debug logging chmod +x script.sh # Make script executable

6-Phase Workflow

Phase 1: Requirements Gathering

Use AskUserQuestion to clarify:

What event should trigger this hook?

  • Tool execution (Pre/Post/Permission) → PreToolUse, PostToolUse, PermissionRequest

  • User input → UserPromptSubmit

  • Response completion → Stop, SubagentStop

  • Session lifecycle → SessionStart, SessionEnd

  • Context management → PreCompact

  • Notifications → Notification

What should happen when triggered?

  • Observe only (logging, metrics)

  • Block/allow based on conditions

  • Modify inputs before execution

  • Add context to prompts

  • Force continuation

Should it block, modify, or just observe?

  • Observer: PostToolUse, Notification, SessionEnd (can't block)

  • Gatekeeper: PreToolUse, PermissionRequest, UserPromptSubmit (can block)

  • Transformer: PreToolUse with updatedInput (can modify)

  • Controller: Stop, SubagentStop (can force continue)

What are the security implications?

  • Will it handle untrusted input?

  • Could it expose sensitive data?

  • Does it need to access external systems?

Phase 2: Event Selection

Match event to use case:

Use Case Best Event

Block dangerous operations PreToolUse

Auto-format code after writes PostToolUse

Validate user prompts UserPromptSubmit

Setup environment SessionStart

Ensure task completion Stop

Log all tool usage PostToolUse with "*" matcher

Protect sensitive files PreToolUse for Write/Edit

Add project context UserPromptSubmit

Determine if matchers are needed:

  • Specific tools? → Use matcher: "Write|Edit"

  • All tools? → Use "*" or omit matcher

  • MCP tools? → Use mcp__server__tool pattern

  • Bash commands? → Use Bash(git:*) pattern

Phase 3: Matcher Design

Matcher Pattern Syntax:

// Exact match (case-sensitive!) "matcher": "Write"

// OR pattern "matcher": "Write|Edit"

// Prefix match "matcher": "Notebook.*"

// Contains match "matcher": ".Read."

// All tools "matcher": "*"

// MCP tools "matcher": "mcp__memory__.*"

// Bash sub-patterns "matcher": "Bash(git:*)"

Common Matcher Patterns:

Pattern Matches

"Write"

Only Write tool

"Write|Edit"

Write OR Edit

"Bash"

All Bash commands

"Bash(git:*)"

Only git commands

"Bash(npm:*)"

Only npm commands

"mcp__.__."

All MCP tools

"." or ""

Everything

Phase 4: Implementation

Choose implementation approach:

Inline command (simple, no external file):

{ "type": "command", "command": "echo "$(date) | $tool_name" >> ~/.claude/audit.log" }

External script (complex logic, reusable):

{ "type": "command", "command": ""$CLAUDE_PROJECT_DIR"/.claude/hooks/validate.sh" }

Prompt-based (LLM evaluation, intelligent decisions):

{ "type": "prompt", "prompt": "Analyze if all tasks are complete: $ARGUMENTS", "timeout": 30 }

Script Template (Bash):

#!/bin/bash set -euo pipefail

Read JSON input from stdin

input=$(cat)

Parse fields with jq

tool_name=$(echo "$input" | jq -r '.tool_name // empty') file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

Your logic here

if [[ "$file_path" == ".env" ]]; then echo "BLOCKED: Cannot modify .env files" >&2 exit 2 fi

Success - output decision

echo '{"decision": "approve"}' exit 0

Script Template (Python):

#!/usr/bin/env python3 import sys import json

Read JSON input from stdin

data = json.load(sys.stdin)

Extract fields

tool_name = data.get('tool_name', '') tool_input = data.get('tool_input', {}) file_path = tool_input.get('file_path', '')

Your logic here

if '.env' in file_path: print("BLOCKED: Cannot modify .env files", file=sys.stderr) sys.exit(2)

Success - output decision

output = {"decision": "approve"} print(json.dumps(output)) sys.exit(0)

Phase 5: Security Hardening

CRITICAL: Hooks execute shell commands with YOUR permissions.

Security Checklist:

  • All variables quoted: "$VAR" not $VAR

  • JSON parsed with jq or json.load (not grep/sed)

  • Paths validated (no .. , normalized)

  • No sensitive data in logs/output

  • No sudo or privilege escalation

  • Script tested manually first

  • Project hooks audited before running

  • Timeout set appropriately

  • Error handling for all failure modes

Secure Patterns:

UNSAFE - injection risk

rm $file_path

SAFE - quoted, prevents flag injection

rm -- "$file_path"

UNSAFE - parsing risk

cat "$input" | grep "field"

SAFE - proper JSON parsing

echo "$input" | jq -r '.field'

Defense in Depth:

  • Input validation (parse JSON properly)

  • Path sanitization (normalize, check boundaries)

  • Output sanitization (no sensitive data)

  • Fail-safe defaults (block on error, not allow)

  • Timeout protection (prevent infinite loops)

Phase 6: Testing

Step 1: Manual Script Testing

Create mock input

cat > /tmp/mock-input.json << 'EOF' { "session_id": "test-123", "hook_event_name": "PreToolUse", "tool_name": "Write", "tool_input": { "file_path": "/path/to/file.txt", "content": "test content" } } EOF

Test script

cat /tmp/mock-input.json | ./my-hook.sh echo "Exit code: $?"

Step 2: Edge Case Testing

  • Empty inputs: {}

  • Missing fields: {"tool_name": "Write"}

  • Malicious inputs: {"tool_input": {"file_path": "; rm -rf /"}}

  • Large inputs: 10KB+ content

  • Unicode: paths with special characters

Step 3: Integration Testing

Start Claude with debug mode

claude --debug

Trigger the tool your hook targets

Watch debug output for hook execution

Step 4: Verification

Check hooks are registered

/hooks

Watch hook execution

claude --debug 2>&1 | grep -i hook

Hook Patterns

Observer Pattern

Log without blocking — use PostToolUse or Notification.

{ "hooks": { "PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "echo "$(date) | $tool_name" >> ~/.claude/audit.log" }] }] } }

Gatekeeper Pattern

Block dangerous actions — use PreToolUse or PermissionRequest.

{ "hooks": { "PreToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "python3 ~/.claude/hooks/file-protector.py" }] }] } }

Transformer Pattern

Modify inputs before execution — use PreToolUse with updatedInput.

In script, output:

output = { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "updatedInput": { "content": add_license_header(original_content) } } } print(json.dumps(output))

Orchestrator Pattern

Coordinate multiple events — combine SessionStart + PreToolUse + PostToolUse.

{ "hooks": { "SessionStart": [{ "matcher": "startup", "hooks": [{"type": "command", "command": "/.claude/hooks/setup-env.sh"}] }], "PreToolUse": [{ "matcher": "Write|Edit", "hooks": [{"type": "command", "command": "/.claude/hooks/validate.sh"}] }], "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{"type": "command", "command": "~/.claude/hooks/format.sh"}] }] } }

Common Pitfalls

  1. Forgetting Exit Code 2 for Blocking

WRONG - exit 1 doesn't block

echo "Error" >&2 exit 1

RIGHT - exit 2 blocks Claude

echo "BLOCKED: reason" >&2 exit 2

  1. Case Sensitivity in Matchers

// WRONG - won't match "Write" tool "matcher": "write"

// RIGHT - case-sensitive match "matcher": "Write"

  1. Unquoted Variables (Injection Risk)

WRONG - command injection vulnerability

rm $file_path

RIGHT - properly quoted

rm -- "$file_path"

  1. Missing Shebang in Scripts

WRONG - no shebang, may fail

set -euo pipefail

RIGHT - explicit interpreter

#!/bin/bash set -euo pipefail

  1. Not Making Scripts Executable

Don't forget!

chmod +x ~/.claude/hooks/my-hook.sh

  1. Forgetting to Quote Paths in JSON

// WRONG - spaces in path will break "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"

// RIGHT - quoted path "command": ""$CLAUDE_PROJECT_DIR"/.claude/hooks/script.sh"

  1. No Error Handling

WRONG - silent failures

input=$(cat) tool=$(echo "$input" | jq -r '.tool_name')

RIGHT - handle errors

input=$(cat) || { echo "Failed to read input" >&2; exit 1; } tool=$(echo "$input" | jq -r '.tool_name') || { echo "Failed to parse JSON" >&2; exit 1; }

  1. Logging Sensitive Data

WRONG - may log secrets

echo "Processing: $input" >> /tmp/debug.log

RIGHT - sanitize before logging

echo "Processing tool: $tool_name" >> /tmp/debug.log

When to Use Hooks

USE hooks for:

  • Security enforcement (block dangerous operations)

  • Code quality automation (format, lint on save)

  • Compliance and auditing (log all actions)

  • Environment setup (consistent configuration)

  • Workflow automation (notifications, integrations)

  • Input validation (prompt checking)

  • Task completion verification

DON'T use hooks for:

  • Adding new capabilities (use Skills)

  • Delegating complex work (use Agents)

  • User-invoked prompts (use Commands)

  • Simple one-off tasks (just ask Claude)

Files in This Skill

Templates (Progressive Complexity)

  • templates/basic-hook.md — Single event, inline command

  • templates/with-scripts.md — External shell scripts

  • templates/with-decisions.md — Permission control, input modification

  • templates/with-prompts.md — LLM-based evaluation

  • templates/production-hooks.md — Complete multi-event system

Examples (18 Complete Hooks)

  • examples/security-hooks.md — Protection, validation, auditing

  • examples/quality-hooks.md — Formatting, linting, testing

  • examples/workflow-hooks.md — Setup, context, notifications

Reference

  • reference/syntax-guide.md — Complete JSON schemas, all events

  • reference/best-practices.md — Security, design, team deployment

  • reference/troubleshooting.md — 10 common issues, testing methodology

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

brand-voice-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

brand-values-development

No summary provided by upstream source.

Repository SourceNeeds Review
General

conversion-psychology

No summary provided by upstream source.

Repository SourceNeeds Review
General

brand-color-psychology

No summary provided by upstream source.

Repository SourceNeeds Review