Back to skills
SkillHub ClubShip Full StackFull Stack

claude-hook-authoring

This skill should be used when creating hooks, automating workflows, or when "PreToolUse", "PostToolUse", "hooks.json", "event handler", or "create hook" are mentioned.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
25
Hot score
88
Updated
March 20, 2026
Overall rating
C2.4
Composite score
2.4
Best-practice grade
N/A

Install command

npx @skill-hub/cli install outfitter-dev-agents-claude-hook-authoring
automationworkflowhooksvalidationclaude-code

Repository

outfitter-dev/agents

Skill path: agent-kit/skills/claude-hook-authoring

This skill should be used when creating hooks, automating workflows, or when "PreToolUse", "PostToolUse", "hooks.json", "event handler", or "create hook" are mentioned.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: outfitter-dev.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install claude-hook-authoring into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/outfitter-dev/agents before adding claude-hook-authoring to shared team environments
  • Use claude-hook-authoring for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: claude-hook-authoring
version: 2.0.0
description: This skill should be used when creating hooks, automating workflows, or when "PreToolUse", "PostToolUse", "hooks.json", "event handler", or "create hook" are mentioned.
user-invocable: true
---

# Claude Hook Authoring

Create event hooks that automate workflows, validate operations, and respond to Claude Code events.

## Hook Types

Two hook execution types:

| Type | Best For | Example |
|------|----------|---------|
| **prompt** | Complex reasoning, context-aware validation | LLM evaluates if action is safe |
| **command** | Deterministic checks, external tools, performance | Bash script validates paths |

**Prompt hooks** (recommended for complex logic):

```json
{
  "type": "prompt",
  "prompt": "Evaluate if this file write is safe: $TOOL_INPUT. Check for sensitive paths, credentials, path traversal. Return 'allow' or 'deny' with reason.",
  "timeout": 30
}
```

**Command hooks** (for deterministic/fast checks):

```json
{
  "type": "command",
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
  "timeout": 10
}
```

## Hook Events

| Event | When | Can Block | Common Uses |
|-------|------|-----------|-------------|
| **PreToolUse** | Before tool executes | Yes | Validate commands, check paths, enforce policies |
| **PostToolUse** | After tool succeeds | No | Auto-format, run linters, update docs |
| **PostToolUseFailure** | After tool fails | No | Error logging, retry logic, notifications |
| **PermissionRequest** | Permission dialog shown | Yes | Auto-allow/deny based on rules |
| **UserPromptSubmit** | User submits prompt | No | Add context, log activity, augment prompts |
| **Notification** | Claude sends notification | No | External alerts, logging |
| **Stop** | Main agent finishes | No | Cleanup, completion notifications |
| **SubagentStart** | Subagent spawns | No | Track subagent usage |
| **SubagentStop** | Subagent finishes | No | Log results, trigger follow-ups |
| **PreCompact** | Before context compacts | No | Backup conversation, preserve context |
| **SessionStart** | Session starts/resumes | No | Load context, show status, init resources |
| **SessionEnd** | Session ends | No | Cleanup, save state, log metrics |

See [references/hook-types.md](references/hook-types.md) for detailed documentation of each event.

## Quick Start

### Auto-Format TypeScript

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts|*.tsx)",
        "hooks": [{
          "type": "command",
          "command": "biome check --write \"$file\"",
          "timeout": 10
        }]
      }
    ]
  }
}
```

### Block Dangerous Commands

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "command",
          "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
          "timeout": 5
        }]
      }
    ]
  }
}
```

**validate-bash.sh**:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -qE '\brm\s+-rf\s+/'; then
  echo "Dangerous command blocked: rm -rf /" >&2
  exit 2  # Exit 2 = block and show error to Claude
fi

exit 0
```

### Smart Validation with Prompt Hook

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{
          "type": "prompt",
          "prompt": "Analyze this file operation for safety. Check: 1) No sensitive paths (/etc, ~/.ssh), 2) No credentials in content, 3) No path traversal (..). Tool input: $TOOL_INPUT. Respond with JSON: {\"decision\": \"allow|deny\", \"reason\": \"...\"}",
          "timeout": 30
        }]
      }
    ]
  }
}
```

## Configuration Locations

| Location | Scope | Committed |
|----------|-------|-----------|
| `.claude/settings.json` | Project (team-shared) | Yes |
| `.claude/settings.local.json` | Project (local only) | No |
| `~/.claude/settings.json` | Personal (all projects) | No |
| `plugin/hooks/hooks.json` | Plugin | Yes |

### Plugin Format (hooks.json)

Uses wrapper structure:

```json
{
  "description": "Plugin hooks for auto-formatting",
  "hooks": {
    "PostToolUse": [...]
  }
}
```

### Settings Format (settings.json)

Direct structure (no wrapper):

```json
{
  "hooks": {
    "PostToolUse": [...]
  }
}
```

## Matchers

Matchers determine which tool invocations trigger the hook. Case-sensitive.

```json
{"matcher": "Write"}                    // Exact match
{"matcher": "Edit|Write"}               // Multiple tools (OR)
{"matcher": "*"}                        // All tools
{"matcher": "Write(*.py)"}              // File pattern
{"matcher": "Write|Edit(*.ts|*.tsx)"}   // Multiple + pattern
{"matcher": "mcp__memory__.*"}          // MCP server tools
{"matcher": "mcp__github__create_issue"} // Specific MCP tool
```

**Lifecycle hooks** (SessionStart, SessionEnd, Stop, Notification) use special matchers:

```json
// SessionStart matchers
{"matcher": "startup"}   // Initial start
{"matcher": "resume"}    // --resume or --continue
{"matcher": "clear"}     // After /clear
{"matcher": "compact"}   // After compaction

// PreCompact matchers
{"matcher": "manual"}    // User triggered /compact
{"matcher": "auto"}      // Automatic compaction
```

See [references/matchers.md](references/matchers.md) for advanced patterns.

## Input Format

All hooks receive JSON on stdin:

```json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "hook_event_name": "PreToolUse",
  "permission_mode": "ask",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/project/src/file.ts",
    "content": "export const foo = 'bar';"
  }
}
```

**Event-specific fields**:
- Tool hooks: `tool_name`, `tool_input`, `tool_result` (PostToolUse)
- UserPromptSubmit: `user_prompt`
- Stop/SubagentStop: `reason`

**Prompt hooks** access fields via: `$TOOL_INPUT`, `$TOOL_RESULT`, `$USER_PROMPT`

### Reading Input

**Bash**:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
```

**Bun/TypeScript**:

```typescript
#!/usr/bin/env bun
const input = await Bun.stdin.json();
const toolName = input.tool_name;
const filePath = input.tool_input?.file_path;
```

## Output Format

### Exit Codes (Simple)

```bash
exit 0   # Success, continue execution
exit 2   # Block operation (PreToolUse only), stderr shown to Claude
exit 1   # Warning, stderr shown to user, continues
```

### JSON Output (Advanced)

```json
{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "Context for Claude",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask",
    "permissionDecisionReason": "Explanation",
    "updatedInput": {"modified": "field"}
  }
}
```

**PreToolUse** can modify tool input via `updatedInput` and control permissions via `permissionDecision`.

## Environment Variables

| Variable | Availability | Description |
|----------|--------------|-------------|
| `$CLAUDE_PROJECT_DIR` | All hooks | Project root directory |
| `$CLAUDE_PLUGIN_ROOT` | Plugin hooks | Plugin root (use for portable paths) |
| `$file` | PostToolUse (Write/Edit) | Path to affected file |
| `$CLAUDE_ENV_FILE` | SessionStart | Write env vars here to persist |
| `$CLAUDE_CODE_REMOTE` | All hooks | Set if running in remote context |

**Plugin hooks** should always use `${CLAUDE_PLUGIN_ROOT}` for portability:

```json
{
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
}
```

**SessionStart** can persist environment variables:

```bash
#!/usr/bin/env bash
# Persist variables for the session
echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
echo "export API_URL=https://api.example.com" >> "$CLAUDE_ENV_FILE"
```

## Component-Scoped Hooks

Skills, agents, and commands can define hooks in frontmatter. These hooks only run when the component is active.

**Supported events**: PreToolUse, PostToolUse, Stop

### Skill with Hooks

```yaml
---
name: my-skill
description: Skill with validation hooks
hooks:
  PreToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: prompt
          prompt: "Validate this write operation for the skill context..."
---
```

### Agent with Hooks

```yaml
---
name: security-reviewer
model: sonnet
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "${CLAUDE_PLUGIN_ROOT}/scripts/validate-bash.sh"
  Stop:
    - matcher: "*"
      hooks:
        - type: prompt
          prompt: "Verify the security review is complete..."
---
```

## Execution Model

**Parallel execution**: All matching hooks run in parallel, not sequentially.

```json
{
  "PreToolUse": [{
    "matcher": "Write",
    "hooks": [
      {"type": "command", "command": "check1.sh"},  // Runs in parallel
      {"type": "command", "command": "check2.sh"},  // Runs in parallel
      {"type": "prompt", "prompt": "Validate..."}   // Runs in parallel
    ]
  }]
}
```

**Implications**:
- Hooks cannot see each other's output
- Non-deterministic ordering
- Design for independence

**Hot-swap limitations**: Hook changes require restarting Claude Code. Editing `hooks.json` or hook scripts does not affect the current session.

## Security Best Practices

1. **Validate all input** - Check for path traversal, sensitive paths, injection
2. **Quote shell variables** - Always use `"$VAR"` not `$VAR`
3. **Set timeouts** - Prevent hanging hooks (default: 60s command, 30s prompt)
4. **Use absolute paths** - Via `$CLAUDE_PROJECT_DIR` or `${CLAUDE_PLUGIN_ROOT}`
5. **Handle errors gracefully** - Use `set -euo pipefail` in bash
6. **Don't log sensitive data** - Filter credentials, tokens, API keys

See [references/security.md](references/security.md) for detailed security patterns.

## Debugging

```bash
# Run Claude with debug output
claude --debug

# Test hook manually
echo '{"tool_name": "Write", "tool_input": {"file_path": "test.ts"}}' | ./.claude/hooks/my-hook.sh

# Check transcript for hook execution
# Press Ctrl+R in Claude Code to view transcript
```

**Common issues**:
- Hook not firing: Check matcher syntax, restart Claude Code
- Permission errors: `chmod +x script.sh`
- Timeout: Increase timeout value or optimize script

## Workflow Patterns

### Pre-Commit Quality Gate

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {"type": "command", "command": "./.claude/hooks/validate-paths.sh"},
          {"type": "command", "command": "./.claude/hooks/check-sensitive.sh"}
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts)",
        "hooks": [
          {"type": "command", "command": "biome check --write \"$file\""},
          {"type": "command", "command": "tsc --noEmit \"$file\""}
        ]
      }
    ]
  }
}
```

### Context Injection

```json
{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup",
      "hooks": [{
        "type": "command",
        "command": "echo \"Branch: $(git branch --show-current)\" && git status --short"
      }]
    }],
    "UserPromptSubmit": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "echo \"Time: $(date '+%Y-%m-%d %H:%M %Z')\""
      }]
    }]
  }
}
```

## References

- [references/hook-types.md](references/hook-types.md) - Detailed documentation for each hook event
- [references/matchers.md](references/matchers.md) - Advanced matcher patterns and MCP tools
- [references/security.md](references/security.md) - Security best practices and validation patterns
- [references/schema.md](references/schema.md) - Complete configuration schema reference
- [references/examples.md](references/examples.md) - Real-world hook implementations

## External Resources

- [Official Hooks Reference](https://code.claude.com/docs/en/hooks)
- [Hooks Guide](https://code.claude.com/docs/en/hooks-guide)
- [Community Examples (disler)](https://github.com/disler/claude-code-hooks-mastery)
- [Claude Code Showcase](https://github.com/ChrisWiles/claude-code-showcase)


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/hook-types.md

```markdown
# Hook Types Reference

Detailed documentation for each Claude Code hook event.

## Tool Hooks

### PreToolUse

Executes **before** a tool runs. Can block or modify tool execution.

**Timing**: After Claude creates tool parameters, before tool executes

**Can block**: Yes (exit code 2 or `permissionDecision: "deny"`)

**Supports**: Both `command` and `prompt` hook types

**Input fields**:
- `tool_name`: Name of the tool being called
- `tool_input`: Parameters being passed to the tool

**Output capabilities**:
- Block execution with exit code 2 or `permissionDecision: "deny"`
- Modify input with `updatedInput` in JSON response
- Ask user with `permissionDecision: "ask"`
- Provide context via `systemMessage`

**Common matchers**:
```json
"Bash"                  // Shell commands
"Write"                 // File writing
"Edit"                  // File editing
"Read"                  // File reading
"Write|Edit"            // Multiple tools
"Write(*.py)"           // File patterns
"mcp__memory__.*"       // MCP tools
"*"                     // All tools
```

**Use cases**:
- Validate bash commands before execution
- Check file paths for security issues
- Block dangerous operations
- Add context before execution
- Enforce security policies
- Log tool invocations
- Modify tool input on the fly

**Example - Block dangerous commands**:

```json
{
  "PreToolUse": [{
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/validate-bash.sh",
      "timeout": 5
    }]
  }]
}
```

**Example - Smart validation with prompt**:

```json
{
  "PreToolUse": [{
    "matcher": "Write|Edit",
    "hooks": [{
      "type": "prompt",
      "prompt": "Analyze this file operation. Check for: 1) sensitive paths, 2) credentials in content, 3) path traversal. Tool: $TOOL_INPUT. Return {\"decision\": \"allow|deny\", \"reason\": \"...\"}",
      "timeout": 30
    }]
  }]
}
```

**Example - Modify tool input**:

```bash
#!/usr/bin/env bash
# Add timestamp to all file writes
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content')

# Add header to content
NEW_CONTENT="// Modified $(date -Iseconds)\n$CONTENT"

cat << EOF
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "file_path": "$FILE_PATH",
      "content": "$NEW_CONTENT"
    }
  }
}
EOF
```

### PostToolUse

Executes **after** a tool completes successfully.

**Timing**: Immediately after tool returns success

**Can block**: No

**Supports**: `command` hook type only

**Input fields**:
- `tool_name`: Name of the tool that ran
- `tool_input`: Parameters that were passed
- `tool_result`: Result returned by the tool

**Special variables**:
- `$file`: Path to affected file (Write/Edit tools only)

**Common matchers**:
```json
"Write|Edit(*.ts)"      // TypeScript files
"Write(*.py)"           // Python files
"Write|Edit"            // Any file modification
"*"                     // All successful tools
```

**Use cases**:
- Auto-format code files
- Run linters
- Update documentation
- Trigger builds
- Send notifications
- Update indexes

**Example - Auto-format TypeScript**:

```json
{
  "PostToolUse": [{
    "matcher": "Write|Edit(*.ts|*.tsx)",
    "hooks": [{
      "type": "command",
      "command": "biome check --write \"$file\"",
      "timeout": 10
    }]
  }]
}
```

**Example - Chain multiple formatters**:

```json
{
  "PostToolUse": [{
    "matcher": "Write|Edit(*.py)",
    "hooks": [
      {"type": "command", "command": "black \"$file\"", "timeout": 10},
      {"type": "command", "command": "isort \"$file\"", "timeout": 5},
      {"type": "command", "command": "mypy \"$file\"", "timeout": 15}
    ]
  }]
}
```

### PostToolUseFailure

Executes **after** a tool fails.

**Timing**: After tool execution fails

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- `tool_name`: Name of the tool that failed
- `tool_input`: Parameters that were passed
- `error`: Error information

**Use cases**:
- Error logging and analytics
- Retry logic
- Failure notifications
- Error recovery
- Debug information collection

**Example - Log failures**:

```json
{
  "PostToolUseFailure": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/log-failure.sh",
      "timeout": 5
    }]
  }]
}
```

### PermissionRequest

Executes when a permission dialog would be shown to the user.

**Timing**: Before showing permission dialog

**Can block**: Yes (via `permissionDecision`)

**Supports**: Both `command` and `prompt` hook types

**Input fields**:
- `tool_name`: Tool requesting permission
- `tool_input`: Parameters being requested

**Output capabilities**:
- Auto-allow with `permissionDecision: "allow"`
- Auto-deny with `permissionDecision: "deny"`
- Show dialog with `permissionDecision: "ask"` (default)

**Use cases**:
- Auto-approve known-safe operations
- Auto-deny high-risk operations
- Implement custom permission policies
- Reduce permission fatigue for trusted patterns

**Example - Auto-approve safe reads**:

```json
{
  "PermissionRequest": [{
    "matcher": "Read",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/auto-approve-reads.sh",
      "timeout": 3
    }]
  }]
}
```

## User Interaction Hooks

### UserPromptSubmit

Executes when user submits a prompt to Claude.

**Timing**: After user submits, before Claude processes

**Can block**: No

**Supports**: Both `command` and `prompt` hook types

**Input fields**:
- `user_prompt`: The prompt text submitted

**Matcher**: Always `*`

**Use cases**:
- Add timestamp or date context
- Add environment information
- Log user activity
- Pre-process or augment prompts
- Add project context
- Skill matching and suggestion

**Example - Add timestamp**:

```json
{
  "UserPromptSubmit": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "echo \"Current time: $(date '+%Y-%m-%d %H:%M:%S %Z')\"",
      "timeout": 2
    }]
  }]
}
```

**Example - Add git context**:

```json
{
  "UserPromptSubmit": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "echo \"Branch: $(git branch --show-current 2>/dev/null || echo 'N/A')\"",
      "timeout": 3
    }]
  }]
}
```

### Notification

Executes when Claude Code sends a notification.

**Timing**: When notification is triggered

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- Notification message and metadata

**Matcher**: Always `*`

**Use cases**:
- Send to external systems (Slack, email)
- Log notifications
- Trigger alerts
- Update dashboards
- Archive important messages
- Text-to-speech announcements

**Example - Slack integration**:

```json
{
  "Notification": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/send-to-slack.sh",
      "timeout": 10
    }]
  }]
}
```

## Agent Lifecycle Hooks

### Stop

Executes when main Claude agent finishes responding.

**Timing**: After Claude completes response

**Can block**: No

**Supports**: Both `command` and `prompt` hook types

**Input fields**:
- `reason`: Why the agent stopped

**Matcher**: Always `*`

**Use cases**:
- Clean up temporary resources
- Send completion notifications
- Update external systems
- Log session metrics
- Archive conversation
- Verify task completion

**Example - Completion notification**:

```json
{
  "Stop": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "echo 'Task completed at $(date +%H:%M)'",
      "timeout": 2
    }]
  }]
}
```

**Example - Verify completeness with prompt**:

```json
{
  "Stop": [{
    "matcher": "*",
    "hooks": [{
      "type": "prompt",
      "prompt": "Review if the task was completed satisfactorily. Check for any unfinished work or follow-up items.",
      "timeout": 30
    }]
  }]
}
```

### SubagentStart

Executes when a subagent (Task tool) spawns.

**Timing**: When subagent is created

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- Subagent metadata

**Matcher**: Always `*`

**Use cases**:
- Track subagent spawning
- Log subagent parameters
- Monitor parallel execution
- Resource allocation

**Example - Track subagent usage**:

```json
{
  "SubagentStart": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/log-subagent-start.sh",
      "timeout": 2
    }]
  }]
}
```

### SubagentStop

Executes when a subagent (Task tool) finishes.

**Timing**: After subagent completes

**Can block**: No

**Supports**: Both `command` and `prompt` hook types

**Input fields**:
- `reason`: Why the subagent stopped
- Subagent result metadata

**Matcher**: Always `*`

**Use cases**:
- Track subagent completion
- Log subagent results
- Trigger follow-up actions
- Update metrics
- Debug subagent behavior

**Example - Log subagent completion**:

```json
{
  "SubagentStop": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/log-subagent-stop.sh",
      "timeout": 3
    }]
  }]
}
```

## Session Lifecycle Hooks

### SessionStart

Executes when session starts or resumes.

**Timing**: At session initialization

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- `reason`: Start type

**Matchers**:
```json
"startup"   // Claude Code starts fresh
"resume"    // Session resumes (--resume or --continue)
"clear"     // After /clear command
"compact"   // After compaction
```

**Special capability**: Persist environment variables via `$CLAUDE_ENV_FILE`

**Use cases**:
- Display welcome message
- Show git status
- Load project context
- Check for updates
- Initialize resources
- Set session-wide variables

**Example - Welcome with git status**:

```json
{
  "SessionStart": [{
    "matcher": "startup",
    "hooks": [{
      "type": "command",
      "command": "echo 'Welcome!' && git status --short",
      "timeout": 5
    }]
  }]
}
```

**Example - Persist environment variables**:

```bash
#!/usr/bin/env bash
# This script runs on SessionStart
# Persist variables for the entire session

# Detect project type and persist
if [[ -f "package.json" ]]; then
  echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
elif [[ -f "Cargo.toml" ]]; then
  echo "export PROJECT_TYPE=rust" >> "$CLAUDE_ENV_FILE"
fi

# Set API endpoints
echo "export API_URL=https://api.example.com" >> "$CLAUDE_ENV_FILE"
```

### SessionEnd

Executes when session ends.

**Timing**: Before session terminates

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- `reason`: End type

**Matchers** (reasons):
```json
"clear"               // User ran /clear
"logout"              // User logged out
"prompt_input_exit"   // Exited during prompt input
"other"               // Other reasons
```

**Use cases**:
- Clean up resources
- Save state
- Log session metrics
- Send completion notifications
- Archive transcripts

**Example - Cleanup**:

```json
{
  "SessionEnd": [{
    "matcher": "*",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/cleanup.sh",
      "timeout": 5
    }]
  }]
}
```

### PreCompact

Executes before conversation compacts.

**Timing**: Before compact operation starts

**Can block**: No

**Supports**: `command` hook type

**Input fields**:
- Compact trigger type

**Matchers**:
```json
"manual"   // User triggered via /compact
"auto"     // Automatic compact (context limit)
```

**Use cases**:
- Backup conversation
- Archive important context
- Update external summaries
- Log compact events
- Prepare for context reset

**Example - Backup before compact**:

```json
{
  "PreCompact": [{
    "matcher": "manual|auto",
    "hooks": [{
      "type": "command",
      "command": "./.claude/hooks/backup-conversation.sh",
      "timeout": 10
    }]
  }]
}
```

## Hook Type Comparison

| Event | Can Block | Prompt Type | Command Type | Common Use |
|-------|-----------|-------------|--------------|------------|
| PreToolUse | Yes | Yes | Yes | Validation, security |
| PostToolUse | No | No | Yes | Formatting, linting |
| PostToolUseFailure | No | No | Yes | Error logging |
| PermissionRequest | Yes | Yes | Yes | Auto-approve/deny |
| UserPromptSubmit | No | Yes | Yes | Context injection |
| Notification | No | No | Yes | External alerts |
| Stop | No | Yes | Yes | Cleanup, verification |
| SubagentStart | No | No | Yes | Tracking |
| SubagentStop | No | Yes | Yes | Logging |
| SessionStart | No | No | Yes | Initialization |
| SessionEnd | No | No | Yes | Cleanup |
| PreCompact | No | No | Yes | Backup |

## Tool Use ID Correlation

PreToolUse and PostToolUse events for the same tool invocation share a tool use ID, allowing you to correlate them:

```bash
#!/usr/bin/env bash
# PreToolUse - save state
INPUT=$(cat)
TOOL_USE_ID=$(echo "$INPUT" | jq -r '.tool_use_id')
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# Save start time for correlation
echo "$(date +%s%N)" > "/tmp/claude-tool-$TOOL_USE_ID.start"
```

```bash
#!/usr/bin/env bash
# PostToolUse - calculate duration
INPUT=$(cat)
TOOL_USE_ID=$(echo "$INPUT" | jq -r '.tool_use_id')

START=$(cat "/tmp/claude-tool-$TOOL_USE_ID.start" 2>/dev/null || echo "0")
END=$(date +%s%N)
DURATION_MS=$(( (END - START) / 1000000 ))

echo "Tool completed in ${DURATION_MS}ms"
rm -f "/tmp/claude-tool-$TOOL_USE_ID.start"
```

```

### references/matchers.md

```markdown
# Matcher Patterns Reference

Matchers determine which tool invocations or events trigger a hook. They are case-sensitive strings that support exact matching, regex patterns, wildcards, and file patterns.

## Matcher Types

### Simple String Match

Match exact tool name:

```json
{"matcher": "Write"}    // Only Write tool
{"matcher": "Edit"}     // Only Edit tool
{"matcher": "Bash"}     // Only Bash tool
{"matcher": "Read"}     // Only Read tool
{"matcher": "Grep"}     // Only Grep tool
{"matcher": "Glob"}     // Only Glob tool
{"matcher": "Task"}     // Only Task tool (subagents)
{"matcher": "WebFetch"} // Only WebFetch tool
{"matcher": "WebSearch"}// Only WebSearch tool
```

### OR Patterns (Pipe)

Match multiple tools with `|`:

```json
{"matcher": "Edit|Write"}              // Edit OR Write
{"matcher": "Read|Grep|Glob"}          // Any read/search operation
{"matcher": "Write|Edit|NotebookEdit"} // Multiple specific tools
{"matcher": "WebFetch|WebSearch"}      // Web operations
```

### Wildcard Match

Match all tools with `*`:

```json
{"matcher": "*"}  // Matches everything
```

**Use cases**:
- Logging all tool usage
- Global validation
- Universal context injection
- Metrics collection

### File Pattern Match

Match tools operating on specific file types with `(pattern)`:

```json
{"matcher": "Write(*.py)"}              // Write Python files
{"matcher": "Edit(*.ts)"}               // Edit TypeScript files
{"matcher": "Write(*.md)"}              // Write Markdown files
{"matcher": "Write|Edit(*.js)"}         // Write or Edit JavaScript
{"matcher": "Write|Edit(*.ts|*.tsx)"}   // TypeScript and TSX files
```

**Supported patterns**:
- `*.ext` - Any file with extension
- `path/*.ext` - Files in specific directory (relative to project)
- `**/*.ext` - Recursive file match

**More examples**:

```json
{"matcher": "Write(*.tsx)"}             // React components
{"matcher": "Write|Edit(*.rs)"}         // Rust files
{"matcher": "Write(src/**/*.ts)"}       // TS files in src/
{"matcher": "Edit(.env*)"}              // .env files
{"matcher": "Write(*.json)"}            // JSON files
{"matcher": "Write|Edit(*.yaml|*.yml)"} // YAML files
```

### Regex Patterns

Full regex support for complex matching:

```json
{"matcher": "^Write$"}           // Exactly "Write", no prefix/suffix
{"matcher": ".*Edit.*"}          // Contains "Edit" anywhere
{"matcher": "Notebook.*"}        // Starts with "Notebook"
{"matcher": "Bash|WebFetch"}     // Bash or WebFetch
```

**Regex features**:
- `|` - OR operator
- `.` - Any character
- `*` - Zero or more
- `+` - One or more
- `^` - Start of string
- `$` - End of string
- `[abc]` - Character class
- `\w` - Word character
- `\d` - Digit

## MCP Tool Matchers

MCP (Model Context Protocol) tools follow naming: `mcp__<server-name>__<tool-name>`

### Match All MCP Tools

```json
{"matcher": "mcp__.*__.*"}  // Any MCP tool from any server
```

### Match Specific Server

```json
{"matcher": "mcp__memory__.*"}      // All memory MCP tools
{"matcher": "mcp__github__.*"}      // All GitHub MCP tools
{"matcher": "mcp__filesystem__.*"}  // All filesystem MCP tools
{"matcher": "mcp__brave-search__.*"}// All Brave search tools
```

### Match Specific Tools

```json
{"matcher": "mcp__github__create_issue"}     // Specific GitHub tool
{"matcher": "mcp__github__create_pull_request"} // Create PR tool
{"matcher": "mcp__memory__add_memory"}       // Add to memory
{"matcher": "mcp__memory__search_memory"}    // Search memory
```

### Complex MCP Patterns

```json
// All delete operations across MCP servers
{"matcher": "mcp__.*__delete.*"}

// Create operations in GitHub
{"matcher": "mcp__github__(create_issue|create_comment|create_pull_request)"}

// All read operations in filesystem
{"matcher": "mcp__filesystem__(read|list|search).*"}

// Dangerous operations to block
{"matcher": "mcp__.*(delete|remove|destroy).*"}
```

## Lifecycle Event Matchers

Some hooks use special matchers for lifecycle events instead of tool names.

### SessionStart Matchers

```json
{"matcher": "startup"}   // Fresh Claude Code start
{"matcher": "resume"}    // Session resume (--resume, --continue)
{"matcher": "clear"}     // After /clear command
{"matcher": "compact"}   // After context compaction
{"matcher": "*"}         // Any session start type
```

### SessionEnd Matchers

```json
{"matcher": "clear"}             // User ran /clear
{"matcher": "logout"}            // User logged out
{"matcher": "prompt_input_exit"} // Exited during prompt
{"matcher": "other"}             // Other reasons
{"matcher": "*"}                 // Any end reason
```

### PreCompact Matchers

```json
{"matcher": "manual"}    // User triggered /compact
{"matcher": "auto"}      // Automatic compaction
{"matcher": "*"}         // Any compact type
```

### Stop/SubagentStop Matchers

```json
{"matcher": "*"}         // Always matches (lifecycle events)
```

## Complex Matcher Examples

### Multiple Tools with File Patterns

```json
// Format Python or TypeScript
{"matcher": "Write|Edit(*.py)|Write|Edit(*.ts)"}

// All code files
{"matcher": "Write|Edit(*.ts|*.tsx|*.js|*.jsx|*.py|*.rs)"}
```

### Excluding Patterns

There's no direct exclusion, but you can handle this in the hook script:

```bash
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Skip test files
if [[ "$FILE_PATH" =~ \.(test|spec)\. ]]; then
  exit 0
fi

# Skip node_modules
if [[ "$FILE_PATH" =~ node_modules/ ]]; then
  exit 0
fi

# Continue with validation...
```

### Combining with Regex

```json
// Bash or any MCP tool
{"matcher": "Bash|mcp__.*__.*"}

// Read operations (multiple tools)
{"matcher": "Read|Grep|Glob|WebFetch"}

// File modifications only
{"matcher": "Write|Edit|NotebookEdit"}
```

## Matcher Debugging

If your hook isn't firing, check:

1. **Case sensitivity**: `Write` works, `write` doesn't
2. **Exact tool names**: Use `claude --debug` to see actual tool names
3. **File patterns**: Ensure the pattern matches the file path format
4. **MCP naming**: Verify server and tool names match exactly

### Testing Matchers

```bash
# See what tools Claude is calling
claude --debug 2>&1 | grep "tool_name"

# Test regex patterns
echo "Write" | grep -E '^Write$'  # Should match
echo "WriteFile" | grep -E '^Write$'  # Should not match
```

## Common Matcher Patterns

### Security Validation

```json
// All file operations
{"matcher": "Write|Edit|Read"}

// Dangerous commands
{"matcher": "Bash"}

// Network operations
{"matcher": "WebFetch|WebSearch|mcp__.*"}
```

### Auto-Formatting

```json
// TypeScript/JavaScript
{"matcher": "Write|Edit(*.ts|*.tsx|*.js|*.jsx)"}

// Python
{"matcher": "Write|Edit(*.py)"}

// Rust
{"matcher": "Write|Edit(*.rs)"}

// All supported
{"matcher": "Write|Edit(*.ts|*.tsx|*.py|*.rs|*.go)"}
```

### Logging

```json
// All tool operations
{"matcher": "*"}

// All MCP operations
{"matcher": "mcp__.*__.*"}

// File operations only
{"matcher": "Write|Edit|Read|Grep|Glob"}
```

### External Integration

```json
// GitHub operations
{"matcher": "mcp__github__.*"}

// Memory operations
{"matcher": "mcp__memory__.*"}

// All external services
{"matcher": "mcp__.*__.*|WebFetch|WebSearch"}
```

```

### references/security.md

```markdown
# Security Best Practices

Comprehensive security guidance for Claude Code hooks.

## Input Validation

### Validate All Input

Always validate and sanitize hook input before use:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Validate input exists
if [[ -z "$FILE_PATH" ]]; then
  echo "Error: file_path missing" >&2
  exit 1
fi

# Validate format
if [[ ! "$FILE_PATH" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
  echo "Error: invalid characters in file path" >&2
  exit 2
fi
```

### Check for Path Traversal

Block directory traversal attacks:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Block path traversal
if echo "$FILE_PATH" | grep -qE '\.\./'; then
  cat << EOF >&2
Path traversal detected: $FILE_PATH
Paths containing '..' are not allowed.
EOF
  exit 2
fi

# Block absolute paths outside project
if [[ "$FILE_PATH" == /* ]] && [[ ! "$FILE_PATH" == "$CLAUDE_PROJECT_DIR"* ]]; then
  echo "Access outside project directory blocked: $FILE_PATH" >&2
  exit 2
fi
```

### Block Sensitive System Paths

Prevent access to system files:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Blocked system paths
BLOCKED_PATHS=(
  '^/etc/'
  '^/root/'
  '^/home/[^/]+/\.ssh/'
  '^/var/log/'
  '^/sys/'
  '^/proc/'
  '^/boot/'
  '^/usr/bin/'
  '^/usr/sbin/'
)

for pattern in "${BLOCKED_PATHS[@]}"; do
  if echo "$FILE_PATH" | grep -qE "$pattern"; then
    cat << EOF >&2
Access to sensitive system path blocked: $FILE_PATH
This path is restricted for security reasons.
EOF
    exit 2
  fi
done
```

### Detect Sensitive Files

Warn or block access to sensitive project files:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Sensitive file patterns
SENSITIVE_PATTERNS=(
  '\.env$'
  '\.env\.'
  'id_rsa'
  'id_ed25519'
  '\.pem$'
  '\.key$'
  '\.p12$'
  'credentials'
  'password'
  'token'
  'secret'
  '\.git/config$'
  '\.npmrc$'
  '\.pypirc$'
)

for pattern in "${SENSITIVE_PATTERNS[@]}"; do
  if echo "$FILE_PATH" | grep -qiE "$pattern"; then
    cat << EOF >&2
Warning: Accessing sensitive file: $FILE_PATH
This file may contain sensitive information.
EOF
    # Could exit 2 to block, or continue with warning
  fi
done
```

## Command Injection Prevention

### Always Quote Variables

```bash
# WRONG - vulnerable to injection
rm $FILE_PATH
cd $DIRECTORY
echo $CONTENT

# CORRECT - properly quoted
rm "$FILE_PATH"
cd "$DIRECTORY"
echo "$CONTENT"
```

### Avoid eval

```bash
# WRONG - dangerous
eval "$USER_COMMAND"

# CORRECT - use specific commands
if [[ "$USER_COMMAND" == "format" ]]; then
  black "$FILE_PATH"
fi
```

### Validate Command Patterns

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Block dangerous command patterns
DANGEROUS_PATTERNS=(
  '\brm\s+-rf\s+/'           # rm -rf /
  '\brm\s+--no-preserve-root' # rm --no-preserve-root
  '\bmkfs\b'                  # filesystem format
  '\bdd\s+if='                # disk destruction
  '\bformat\s+[cC]:'          # Windows format
  '>\s*/dev/sd[a-z]'          # overwrite disk
  ':()\{\s*:\|\:&\s*\};:'     # Fork bomb
  '\bchmod\s+777\s+/'         # Dangerous permissions
  '\bchown\s+.*\s+/'          # System ownership change
  '\bcurl\s+.*\|\s*bash'      # Pipe to bash
  '\bwget\s+.*\|\s*bash'      # Pipe to bash
  '\bsudo\s+rm'               # Sudo rm
  '\bgit\s+push\s+--force\s+origin\s+main' # Force push main
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$pattern"; then
    cat << EOF >&2
Dangerous command blocked: $COMMAND
Pattern matched: $pattern
EOF
    exit 2
  fi
done
```

## Path Security

### Use Absolute Paths

Always construct paths from known roots:

```bash
#!/usr/bin/env bash
# Use CLAUDE_PROJECT_DIR for project paths
SCRIPT_PATH="$CLAUDE_PROJECT_DIR/.claude/hooks/helper.sh"

# Use CLAUDE_PLUGIN_ROOT for plugin paths
PLUGIN_SCRIPT="${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"

# Never rely on relative paths
# BAD: ./scripts/validate.sh
# GOOD: "$CLAUDE_PROJECT_DIR/.claude/scripts/validate.sh"
```

### Validate Script Existence

```bash
#!/usr/bin/env bash
SCRIPT_PATH="$CLAUDE_PROJECT_DIR/.claude/hooks/helper.sh"

# Check exists
if [[ ! -f "$SCRIPT_PATH" ]]; then
  echo "Error: script not found: $SCRIPT_PATH" >&2
  exit 1
fi

# Check executable
if [[ ! -x "$SCRIPT_PATH" ]]; then
  echo "Error: script not executable: $SCRIPT_PATH" >&2
  exit 1
fi

# Execute safely
"$SCRIPT_PATH" "$@"
```

### Resolve Symlinks

```bash
#!/usr/bin/env bash
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')

# Resolve symlinks to check actual destination
REAL_PATH=$(realpath "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")

# Check the real path is within project
if [[ ! "$REAL_PATH" == "$CLAUDE_PROJECT_DIR"* ]]; then
  echo "Symlink points outside project: $FILE_PATH -> $REAL_PATH" >&2
  exit 2
fi
```

## Sensitive Data Protection

### Never Log Sensitive Data

```bash
#!/usr/bin/env bash
INPUT=$(cat)

# WRONG - logs everything including secrets
echo "Input: $INPUT" >> /tmp/debug.log

# CORRECT - log only safe fields
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
echo "Tool: $TOOL_NAME" >> /tmp/debug.log

# CORRECT - filter sensitive fields before logging
echo "$INPUT" | jq 'del(.tool_input.password, .tool_input.api_key, .tool_input.token)' >> /tmp/debug.log
```

### Sanitize Output

```bash
#!/usr/bin/env bash
INPUT=$(cat)
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')

# Check for secrets in content
if echo "$CONTENT" | grep -qiE '(password|api_key|secret|token)\s*[=:]\s*\S+'; then
  echo "Warning: Potential secret detected in content" >&2
  # Could block or just warn
fi
```

### Protect Environment Variables

```bash
#!/usr/bin/env bash
# Don't expose sensitive env vars

# WRONG
echo "API_KEY=$API_KEY"
env | grep -i secret

# CORRECT - never print secrets
echo "API key configured: $([ -n "$API_KEY" ] && echo "yes" || echo "no")"
```

## Timeout Protection

### Set Appropriate Timeouts

```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "./.claude/hooks/validate.sh",
        "timeout": 5
      }]
    }],
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "./.claude/hooks/format.sh",
        "timeout": 30
      }]
    }]
  }
}
```

**Guidelines**:
- Quick validation: 3-5 seconds
- Formatting: 10-30 seconds
- Network operations: 30-60 seconds
- Default: 60 seconds for command, 30 seconds for prompt

### Handle Timeouts Gracefully

```bash
#!/usr/bin/env bash
set -euo pipefail

# Set internal timeout for network operations
timeout 10 curl -s https://api.example.com/validate || {
  echo "API validation skipped (timeout)" >&2
  exit 0  # Don't block on timeout
}
```

## Error Handling

### Use Strict Mode

```bash
#!/usr/bin/env bash
set -euo pipefail  # Exit on error, undefined vars, pipe failures

# Also consider:
set -E  # Inherit ERR trap in functions
trap 'echo "Error on line $LINENO" >&2' ERR
```

### Validate Dependencies

```bash
#!/usr/bin/env bash
set -euo pipefail

# Check required tools exist
for cmd in jq git curl; do
  if ! command -v "$cmd" &>/dev/null; then
    echo "Error: $cmd not installed" >&2
    exit 1
  fi
done
```

### Handle JSON Parsing Errors

```bash
#!/usr/bin/env bash
set -euo pipefail

# Read input with error handling
INPUT=$(cat) || {
  echo "Error: failed to read stdin" >&2
  exit 1
}

# Parse with validation
if ! echo "$INPUT" | jq empty 2>/dev/null; then
  echo "Error: invalid JSON input" >&2
  exit 1
fi

TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
if [[ -z "$TOOL_NAME" ]]; then
  echo "Error: tool_name missing" >&2
  exit 1
fi
```

## Permission Control

### PreToolUse Permission Decisions

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Auto-approve reads of non-sensitive files
if [[ "$TOOL_NAME" == "Read" ]] && [[ ! "$FILE_PATH" =~ \.(env|key|pem)$ ]]; then
  cat << EOF
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow"
  }
}
EOF
  exit 0
fi

# Ask for writes to core files
if [[ "$FILE_PATH" =~ src/core/ ]]; then
  cat << EOF
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Write to core module requires confirmation"
  }
}
EOF
  exit 0
fi

# Default: allow
exit 0
```

### PermissionRequest Hook

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# Auto-deny certain operations
if [[ "$TOOL_NAME" =~ (delete|destroy|remove) ]]; then
  cat << EOF
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive operations require manual approval"
  }
}
EOF
  exit 0
fi
```

## Audit Trail

### Log All Operations

```bash
#!/usr/bin/env bash
set -euo pipefail

AUDIT_FILE="$CLAUDE_PROJECT_DIR/.claude/audit.log"

INPUT=$(cat)
TIMESTAMP=$(date -Iseconds)
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "N/A"')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // "N/A"')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Create audit entry (filter sensitive data)
AUDIT_ENTRY=$(jq -n \
  --arg ts "$TIMESTAMP" \
  --arg event "$HOOK_EVENT" \
  --arg tool "$TOOL_NAME" \
  --arg file "$FILE_PATH" \
  --arg session "$SESSION_ID" \
  --arg user "$USER" \
  '{
    timestamp: $ts,
    event: $event,
    tool: $tool,
    file: $file,
    session: $session,
    user: $user
  }')

echo "$AUDIT_ENTRY" >> "$AUDIT_FILE"

# Rotate: keep only last 10000 entries
tail -n 10000 "$AUDIT_FILE" > "$AUDIT_FILE.tmp" && mv "$AUDIT_FILE.tmp" "$AUDIT_FILE"

exit 0
```

## Security Checklist

### Before Deploying Hooks

- [ ] All input validated and sanitized
- [ ] Path traversal attacks blocked
- [ ] Sensitive system paths protected
- [ ] All shell variables quoted
- [ ] No eval or command injection vectors
- [ ] Sensitive data not logged
- [ ] Appropriate timeouts set
- [ ] Dependencies validated
- [ ] Error handling robust
- [ ] Audit trail enabled

### Regular Security Review

- [ ] Review hook scripts for vulnerabilities
- [ ] Check for hardcoded secrets
- [ ] Verify timeout values are appropriate
- [ ] Audit logged data for sensitive info leaks
- [ ] Update blocked patterns for new threats
- [ ] Test hooks with malicious input

```

### references/schema.md

```markdown
# Hook Reference

Comprehensive technical reference for Claude Code event hooks.

## Table of Contents

1. [Hook Configuration Schema](#hook-configuration-schema)
2. [Hook Events](#hook-events)
3. [Matcher Patterns](#matcher-patterns)
4. [Input Format](#input-format)
5. [Output Format](#output-format)
6. [Environment Variables](#environment-variables)
7. [Exit Codes](#exit-codes)
8. [Hook Chaining](#hook-chaining)
9. [Security Best Practices](#security-best-practices)
10. [MCP Integration](#mcp-integration)
11. [Plugin Hooks](#plugin-hooks)
12. [Advanced Patterns](#advanced-patterns)

## Hook Configuration Schema

### Location

Hooks are configured in JSON settings files:

| Location | Scope | Committed |
|----------|-------|-----------|
| `~/.claude/settings.json` | Personal (all projects) | No |
| `.claude/settings.json` | Project (shared with team) | Yes |
| `.claude/settings.local.json` | Project (local overrides) | No |
| `plugin/hooks/hooks.json` | Plugin | Yes |

### Basic Structure

```json
{
  "hooks": {
    "<EventName>": [
      {
        "matcher": "<ToolPattern>",
        "hooks": [
          {
            "type": "command",
            "command": "<shell-command>",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

### Field Reference

#### `hooks` (root)

**Type**: Object
**Required**: Yes
**Description**: Root object containing all hook definitions

```json
{
  "hooks": {
    // Event configurations here
  }
}
```

#### Event Name Keys

**Type**: String (key)
**Required**: At least one
**Valid values**:
- `PreToolUse`
- `PostToolUse`
- `UserPromptSubmit`
- `Notification`
- `Stop`
- `SubagentStop`
- `PreCompact`
- `SessionStart`
- `SessionEnd`

**Description**: Event type that triggers the hook

```json
{
  "hooks": {
    "PreToolUse": [...],
    "PostToolUse": [...],
    "SessionStart": [...]
  }
}
```

#### Event Configuration Array

**Type**: Array of objects
**Description**: Array of matcher/hooks pairs for an event

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.py)",
        "hooks": [...]
      },
      {
        "matcher": "Edit(*.ts)",
        "hooks": [...]
      }
    ]
  }
}
```

#### `matcher`

**Type**: String
**Required**: Yes
**Description**: Pattern to match tools or event types

**Syntax options:**
- Simple: `"Write"` - Exact tool name
- Regex: `"Edit|Write"` - OR pattern
- Wildcard: `"*"` - All tools
- File pattern: `"Write(*.py)"` - File extension
- MCP: `"mcp__server__tool"` - MCP tool pattern

```json
{"matcher": "Write|Edit"}
```

#### `hooks` (nested)

**Type**: Array of objects
**Required**: Yes
**Description**: Commands to execute when matcher triggers

```json
{
  "hooks": [
    {
      "type": "command",
      "command": "black \"$file\"",
      "timeout": 30
    }
  ]
}
```

#### `type`

**Type**: String
**Required**: Yes
**Valid values**: `"command"`
**Description**: Hook execution type (currently only "command" supported)

#### `command`

**Type**: String
**Required**: Yes
**Description**: Shell command to execute

**Features:**
- Variable expansion: `$file`, `$CLAUDE_PROJECT_DIR`
- Stdin: Receives JSON input
- Stdout: Shown to user
- Stderr: Error messages
- Exit code: Controls behavior

```json
{
  "type": "command",
  "command": "./.claude/hooks/format-code.sh"
}
```

#### `timeout`

**Type**: Number (seconds)
**Required**: No
**Default**: 30
**Description**: Maximum execution time

```json
{
  "type": "command",
  "command": "./slow-operation.sh",
  "timeout": 60
}
```

### Complete Example

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
            "timeout": 5
          }
        ]
      },
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-paths.sh",
            "timeout": 3
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write(*.ts)",
        "hooks": [
          {
            "type": "command",
            "command": "biome check --write \"$file\"",
            "timeout": 10
          }
        ]
      },
      {
        "matcher": "Write(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "black \"$file\"",
            "timeout": 10
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Session started' && git status",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

## Hook Events

### PreToolUse

Executes **before** a tool runs. Can block or modify execution.

**Timing**: After Claude creates tool parameters, before tool execution

**Input**: Tool name and full input parameters

**Can block**: Yes (exit code 2)

**Common matchers**:
- `Bash` - Shell commands
- `Write` - File writing
- `Edit` - File editing
- `Read` - File reading
- `Grep` - Content search
- `Glob` - File patterns
- `WebFetch` - Web operations
- `WebSearch` - Web search
- `Task` - Subagent tasks
- `*` - All tools

**Use cases**:
- Validate bash commands before execution
- Check file paths for security issues
- Block dangerous operations
- Add context before execution
- Enforce security policies
- Log tool invocations

**Example**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/validate-bash.sh",
          "timeout": 5
        }]
      }
    ]
  }
}
```

### PostToolUse

Executes **after** a tool completes successfully.

**Timing**: Immediately after tool returns success

**Input**: Tool name, input parameters, and execution result

**Can block**: No (but can report issues)

**Common matchers**:
- `Write(*.ext)` - Specific file types
- `Edit(*.ext)` - Specific file types
- `Write|Edit` - Any file modification
- `*` - All successful tools

**Use cases**:
- Auto-format code files
- Run linters
- Update documentation
- Trigger builds
- Send notifications
- Update indexes

**Example**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts)",
        "hooks": [{
          "type": "command",
          "command": "biome check --write \"$file\"",
          "timeout": 10
        }]
      }
    ]
  }
}
```

### UserPromptSubmit

Executes when user submits a prompt to Claude.

**Timing**: After user submits, before Claude processes

**Input**: User prompt text and session metadata

**Can block**: No

**Matcher**: Always `*`

**Use cases**:
- Add timestamp or date context
- Add environment information
- Log user activity
- Pre-process or augment prompts
- Add project context

**Example**:

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/add-context.sh",
          "timeout": 2
        }]
      }
    ]
  }
}
```

### Notification

Executes when Claude Code sends a notification.

**Timing**: When notification is triggered

**Input**: Notification message and metadata

**Can block**: No

**Matcher**: Always `*`

**Use cases**:
- Send to external systems (Slack, email)
- Log notifications
- Trigger alerts
- Update dashboards
- Archive important messages

**Example**:

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/send-to-slack.sh",
          "timeout": 5
        }]
      }
    ]
  }
}
```

### Stop

Executes when main Claude agent finishes responding.

**Timing**: After Claude completes response

**Input**: Session metadata and completion reason

**Can block**: No

**Matcher**: Always `*`

**Use cases**:
- Clean up temporary resources
- Send completion notifications
- Update external systems
- Log session metrics
- Archive conversation

**Example**:

```json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/on-completion.sh",
          "timeout": 5
        }]
      }
    ]
  }
}
```

### SubagentStop

Executes when a subagent (Task tool) finishes.

**Timing**: After subagent completes

**Input**: Subagent metadata and result

**Can block**: No

**Matcher**: Always `*`

**Use cases**:
- Track subagent usage
- Log subagent results
- Trigger follow-up actions
- Update metrics
- Debug subagent behavior

**Example**:

```json
{
  "hooks": {
    "SubagentStop": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/log-subagent.sh",
          "timeout": 3
        }]
      }
    ]
  }
}
```

### PreCompact

Executes before conversation compacts.

**Timing**: Before compact operation starts

**Input**: Compact trigger type

**Can block**: No

**Matchers**:
- `manual` - User triggered via `/compact`
- `auto` - Automatic compact

**Use cases**:
- Backup conversation
- Archive important context
- Update external summaries
- Log compact events
- Prepare for reset

**Example**:

```json
{
  "hooks": {
    "PreCompact": [
      {
        "matcher": "manual",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/backup-conversation.sh",
          "timeout": 10
        }]
      }
    ]
  }
}
```

### SessionStart

Executes when session starts or resumes.

**Timing**: At session initialization

**Input**: Session start reason

**Can block**: No

**Matchers**:
- `startup` - Claude Code starts
- `resume` - Session resumes (`--resume`, `--continue`)
- `clear` - After `/clear` command
- `compact` - After compact operation

**Use cases**:
- Display welcome message
- Show git status
- Load project context
- Check for updates
- Initialize resources

**Example**:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [{
          "type": "command",
          "command": "echo 'Welcome!' && git status",
          "timeout": 5
        }]
      }
    ]
  }
}
```

### SessionEnd

Executes when session ends.

**Timing**: Before session terminates

**Input**: End reason

**Can block**: No

**Matchers** (reasons):
- `clear` - User ran `/clear`
- `logout` - User logged out
- `prompt_input_exit` - Exited during prompt input
- `other` - Other reasons

**Use cases**:
- Clean up resources
- Save state
- Log session metrics
- Send completion notifications
- Archive transcripts

**Example**:

```json
{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/cleanup.sh",
          "timeout": 5
        }]
      }
    ]
  }
}
```

## Matcher Patterns

### Simple String Match

Match exact tool name:

```json
{"matcher": "Write"}   // Only Write tool
{"matcher": "Edit"}    // Only Edit tool
{"matcher": "Bash"}    // Only Bash tool
{"matcher": "Read"}    // Only Read tool
```

### Regex Patterns

Use `|` for OR logic:

```json
{"matcher": "Edit|Write"}              // Edit OR Write
{"matcher": "Read|Grep|Glob"}          // Any read operation
{"matcher": "Notebook.*"}              // Any Notebook tool
{"matcher": "Write|Edit|NotebookEdit"} // Multiple tools
```

**Regex features:**
- `|` - OR operator
- `.` - Any character
- `*` - Zero or more
- `+` - One or more
- `^` - Start of string
- `$` - End of string

**Examples:**

```json
{"matcher": "^Write$"}        // Exactly "Write", no prefix/suffix
{"matcher": ".*Edit.*"}       // Contains "Edit" anywhere
{"matcher": "Bash|WebFetch"}  // Bash or WebFetch
```

### Wildcard Match

Match all tools:

```json
{"matcher": "*"}  // Matches everything
```

**Use cases:**
- Logging all tool usage
- Global validation
- Universal context injection
- Metrics collection

### File Pattern Match

Match tools with specific file patterns:

```json
{"matcher": "Write(*.py)"}         // Write Python files
{"matcher": "Edit(*.ts)"}          // Edit TypeScript files
{"matcher": "Write(*.md)"}         // Write Markdown files
{"matcher": "Write|Edit(*.js)"}    // Write or Edit JavaScript
```

**Supported patterns:**
- `*.ext` - Any file with extension
- `path/*.ext` - Files in specific directory
- `**/*.ext` - Recursive file match

**Examples:**

```json
{"matcher": "Write(*.tsx)"}            // React components
{"matcher": "Write|Edit(*.rs)"}        // Rust files
{"matcher": "Write(src/**/*.ts)"}      // TS files in src/
{"matcher": "Edit(.env*)"}             // .env files
```

### MCP Tool Match

Match MCP server tools:

```json
{"matcher": "mcp__memory__.*"}        // Any memory MCP tool
{"matcher": "mcp__github__.*"}        // Any GitHub MCP tool
{"matcher": "mcp__.*__.*"}            // Any MCP tool
{"matcher": "mcp__linear__create_issue"}  // Specific MCP tool
```

**MCP tool naming**: `mcp__<server-name>__<tool-name>`

**Examples:**

```json
// Match all memory operations
{"matcher": "mcp__memory__.*"}

// Match specific GitHub operations
{"matcher": "mcp__github__(create_issue|create_comment)"}

// Match all MCP tools
{"matcher": "mcp__.*__.*"}

// Match Linear issue creation
{"matcher": "mcp__linear__create_issue"}
```

### Complex Matchers

Combine patterns with regex:

```json
// Format Python or TypeScript files
{"matcher": "Write|Edit(*.py)|Write|Edit(*.ts)"}

// Format code files, exclude tests
{"matcher": "Write|Edit(*.ts|*.py)"}

// Bash or any MCP tool
{"matcher": "Bash|mcp__.*__.*"}

// Read operations (multiple tools)
{"matcher": "Read|Grep|Glob|WebFetch"}
```

## Input Format

### JSON Schema

Hooks receive JSON on stdin:

```typescript
interface HookInput {
  session_id: string;
  transcript_path: string;
  cwd: string;
  hook_event_name: string;
  tool_name?: string;
  tool_input?: Record<string, any>;
  reason?: string;
  [key: string]: any;
}
```

### Common Fields

#### All Events

```json
{
  "session_id": "abc123-def456-ghi789",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "hook_event_name": "PreToolUse"
}
```

#### Tool Events (PreToolUse, PostToolUse)

```json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/project/root",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/project/root/src/file.ts",
    "content": "export const foo = 'bar';"
  }
}
```

#### Session Events

```json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/project/root",
  "hook_event_name": "SessionStart",
  "reason": "startup"
}
```

### Tool-Specific Input

#### Bash Tool

```json
{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git status",
    "description": "Check git status"
  }
}
```

#### Write Tool

```json
{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/absolute/path/to/file.ts",
    "content": "file contents here"
  }
}
```

#### Edit Tool

```json
{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/absolute/path/to/file.ts",
    "old_string": "const foo = 'old';",
    "new_string": "const foo = 'new';",
    "replace_all": false
  }
}
```

#### Read Tool

```json
{
  "tool_name": "Read",
  "tool_input": {
    "file_path": "/absolute/path/to/file.ts",
    "offset": 0,
    "limit": 2000
  }
}
```

### Reading Input

#### Bash

```bash
#!/usr/bin/env bash
set -euo pipefail

# Read entire input
INPUT=$(cat)

# Parse with jq
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Check if field exists
if [[ -z "$TOOL_NAME" ]]; then
  echo "Error: tool_name not found" >&2
  exit 1
fi
```

#### Bun/TypeScript

```typescript
#!/usr/bin/env bun
import { stdin } from "process";

interface HookInput {
  session_id: string;
  tool_name?: string;
  tool_input?: Record<string, any>;
  hook_event_name: string;
}

// Read stdin
const chunks: Buffer[] = [];
for await (const chunk of stdin) {
  chunks.push(chunk);
}

const input: HookInput = JSON.parse(Buffer.concat(chunks).toString());

// Access fields
const toolName = input.tool_name;
const filePath = input.tool_input?.file_path;

// Validate
if (!toolName) {
  console.error("Error: tool_name missing");
  process.exit(1);
}
```

#### Python

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

# Read input
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error parsing JSON: {e}", file=sys.stderr)
    sys.exit(1)

# Access fields
tool_name = input_data.get("tool_name", "")
file_path = input_data.get("tool_input", {}).get("file_path", "")

# Validate
if not tool_name:
    print("Error: tool_name missing", file=sys.stderr)
    sys.exit(1)
```

## Output Format

### Exit Codes (Simple)

Most common approach:

```bash
#!/usr/bin/env bash

# Success - continue execution
echo "Validation passed"
exit 0

# Blocking error - show to Claude
echo "Error: dangerous operation detected" >&2
exit 2

# Non-blocking error - show to user
echo "Warning: minor issue detected" >&2
exit 1
```

**Behavior:**

| Exit Code | Behavior | Stdout | Stderr |
|-----------|----------|--------|--------|
| 0 | Success | Shown to user | Ignored |
| 2 | Block (PreToolUse only) | Ignored | Shown to Claude |
| 1 or other | Non-blocking error | Ignored | Shown to user |

### JSON Output (Advanced)

For complex responses:

```json
{
  "continue": true,
  "stopReason": "Optional stop message",
  "suppressOutput": false,
  "systemMessage": "Warning or info message",
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Dangerous operation",
    "additionalContext": "Context for Claude"
  }
}
```

#### Field Reference

**`continue`** (boolean)
- `true`: Continue execution
- `false`: Stop execution

**`stopReason`** (string)
- Message explaining why stopped
- Shown to user

**`suppressOutput`** (boolean)
- `true`: Hide stdout from user
- `false`: Show stdout

**`systemMessage`** (string)
- Info/warning message
- Shown to user

**`decision`** (string)
- `"block"`: Block operation (PreToolUse)
- `"approve"`: Approve operation
- `undefined`: No decision

**`reason`** (string)
- Explanation for decision
- Shown in context

**`hookSpecificOutput`** (object)
- Event-specific data
- See below for details

#### PreToolUse JSON Output

```json
{
  "continue": false,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Path traversal detected in file path",
    "additionalContext": "The file path contains '..' which could allow directory traversal"
  }
}
```

**Permission decisions:**
- `"allow"`: Approve tool use
- `"deny"`: Block tool use
- `"ask"`: Ask user for permission

#### Example: Bash with JSON Output

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Check for path traversal
if echo "$FILE_PATH" | grep -q '\.\.'; then
  # Output JSON response
  cat << EOF
{
  "continue": false,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Path traversal detected",
    "additionalContext": "File path contains '..' which is not allowed"
  }
}
EOF
  exit 0
fi

# Approve
echo "Path validation passed"
exit 0
```

## Environment Variables

> **See also:** [Environment Variables Reference](../../shared/rules/ENV-VARS.md) for comprehensive documentation on `${CLAUDE_PLUGIN_ROOT}` vs `$CLAUDE_PROJECT_DIR`.

### Available Variables

#### `$CLAUDE_PROJECT_DIR`

**Type**: String
**Availability**: All hooks
**Description**: Absolute path to project root directory

```bash
"$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
```

**Use cases:**
- Reference project scripts
- Construct relative paths
- Check project structure

#### `$file`

**Type**: String
**Availability**: PostToolUse hooks for Write/Edit tools
**Description**: Absolute path to affected file

```bash
"biome check --write \"$file\""
```

**Use cases:**
- Auto-format files
- Run linters
- Update related files

#### `${CLAUDE_PLUGIN_ROOT}`

**Type**: String
**Availability**: Plugin hooks only
**Description**: Absolute path to plugin root directory

```json
{
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
}
```

**Use cases:**
- Reference plugin scripts
- Load plugin resources
- Access plugin data

### Custom Variables

Define in settings.json:

```json
{
  "env": {
    "CUSTOM_VAR": "value",
    "API_KEY": "secret"
  },
  "hooks": {
    "PostToolUse": [...]
  }
}
```

Access in hooks:

```bash
#!/usr/bin/env bash
echo "Custom var: $CUSTOM_VAR"
```

## Exit Codes

### Standard Exit Codes

```bash
0   - Success, continue
1   - Non-blocking error
2   - Blocking error (PreToolUse only)
3+ - Non-blocking error
```

### Exit Code Behavior

#### Exit 0 (Success)

```bash
#!/usr/bin/env bash
echo "Validation passed"
exit 0
```

**Behavior:**
- Execution continues
- Stdout shown to user
- Stderr ignored

**Use for:**
- Successful validation
- Informational output
- Non-critical messages

#### Exit 1 (Warning)

```bash
#!/usr/bin/env bash
echo "Warning: potential issue detected" >&2
exit 1
```

**Behavior:**
- Execution continues
- Stderr shown to user
- Stdout ignored

**Use for:**
- Warnings
- Non-critical issues
- Suggestions

#### Exit 2 (Block)

```bash
#!/usr/bin/env bash
echo "Error: dangerous operation blocked" >&2
exit 2
```

**Behavior:**
- PreToolUse: Blocks tool execution
- PostToolUse: Reports error (doesn't block)
- Stderr shown to Claude
- Stdout ignored

**Use for:**
- Security violations
- Policy enforcement
- Dangerous operations

### Error Handling

Always handle errors gracefully:

```bash
#!/usr/bin/env bash
set -euo pipefail

# Check dependencies
if ! command -v jq &>/dev/null; then
  echo "Error: jq not installed" >&2
  exit 1
fi

# Validate input
INPUT=$(cat) || {
  echo "Error: failed to read stdin" >&2
  exit 1
}

# Parse with error handling
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty') || {
  echo "Error: failed to parse JSON" >&2
  exit 1
}

# Validate required fields
if [[ -z "$TOOL_NAME" ]]; then
  echo "Error: tool_name missing" >&2
  exit 1
fi
```

## Hook Chaining

### Multiple Hooks per Event

Execute multiple hooks sequentially:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.ts)",
        "hooks": [
          {
            "type": "command",
            "command": "biome check --write \"$file\"",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "tsc --noEmit \"$file\"",
            "timeout": 15
          },
          {
            "type": "command",
            "command": "./.claude/hooks/update-index.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

**Execution:**
- Runs in order
- If one fails (non-zero exit), subsequent hooks still run
- All output collected and shown

### Cross-Event Coordination

Use shared state for coordination:

```bash
# PreToolUse: Record operation
#!/usr/bin/env bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
echo "$TOOL_NAME $(date +%s)" >> /tmp/claude-operations.log
exit 0
```

```bash
# PostToolUse: Update metrics
#!/usr/bin/env bash
OPERATIONS=$(wc -l < /tmp/claude-operations.log)
echo "Total operations: $OPERATIONS" >&2
exit 0
```

## Security Best Practices

### 1. Input Validation

Always validate and sanitize inputs:

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Check for path traversal
if echo "$FILE_PATH" | grep -qE '\.\.|^/etc/|^/root/|^/home/[^/]+/\.ssh/'; then
  echo "❌ Dangerous path detected: $FILE_PATH" >&2
  exit 2
fi

# Check for sensitive files
if echo "$FILE_PATH" | grep -qE '\.env$|\.git/config|id_rsa|credentials'; then
  echo "❌ Sensitive file access blocked: $FILE_PATH" >&2
  exit 2
fi

# Validate file extension
if [[ "$FILE_PATH" =~ \.(exe|sh|bin)$ ]]; then
  echo "⚠ Warning: executable file" >&2
fi
```

### 2. Command Injection Prevention

Always quote variables:

```bash
# ❌ WRONG - vulnerable to injection
rm $FILE_PATH

# ✅ CORRECT - properly quoted
rm "$FILE_PATH"

# ❌ WRONG - vulnerable
eval "$COMMAND"

# ✅ CORRECT - use array or avoid eval
bash -c "$COMMAND"
```

### 3. Path Security

Use absolute paths and validate:

```bash
#!/usr/bin/env bash

# Get absolute path
SCRIPT_PATH="$CLAUDE_PROJECT_DIR/.claude/hooks/helper.sh"

# Validate script exists
if [[ ! -f "$SCRIPT_PATH" ]]; then
  echo "Error: script not found: $SCRIPT_PATH" >&2
  exit 1
fi

# Validate script is executable
if [[ ! -x "$SCRIPT_PATH" ]]; then
  echo "Error: script not executable: $SCRIPT_PATH" >&2
  exit 1
fi

# Execute safely
"$SCRIPT_PATH" "$@"
```

### 4. Sensitive Data Protection

Never log or expose sensitive data:

```bash
#!/usr/bin/env bash
INPUT=$(cat)

# ❌ WRONG - logs sensitive data
echo "Input: $INPUT" >> /tmp/debug.log

# ✅ CORRECT - log only safe fields
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
echo "Tool: $TOOL_NAME" >> /tmp/debug.log

# ✅ Filter sensitive fields
echo "$INPUT" | jq 'del(.tool_input.password, .tool_input.api_key)' >> /tmp/debug.log
```

### 5. Timeout Protection

Set appropriate timeouts:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/validate.sh",
          "timeout": 5
        }]
      }
    ],
    "PostToolUse": [
      {
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/format.sh",
          "timeout": 30
        }]
      }
    ]
  }
}
```

**Guidelines:**
- Validation: 3-5 seconds
- Formatting: 10-30 seconds
- Network operations: 30-60 seconds
- Heavy operations: Consider running async

### 6. Error Recovery

Handle failures gracefully:

```bash
#!/usr/bin/env bash
set -euo pipefail

# Trap errors
trap 'echo "Error on line $LINENO" >&2' ERR

# Validate dependencies
for cmd in jq git; do
  if ! command -v "$cmd" &>/dev/null; then
    echo "Error: $cmd not installed" >&2
    exit 1
  fi
done

# Main logic with error handling
if ! INPUT=$(cat 2>&1); then
  echo "Error: failed to read stdin" >&2
  exit 1
fi

# Parse with validation
if ! TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name' 2>&1); then
  echo "Error: invalid JSON input" >&2
  exit 1
fi
```

## MCP Integration

### Matching MCP Tools

MCP tools follow pattern: `mcp__<server>__<tool>`

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/log-memory-ops.sh"
        }]
      },
      {
        "matcher": "mcp__github__create_issue",
        "hooks": [{
          "type": "command",
          "command": "./.claude/hooks/validate-issue.sh"
        }]
      }
    ]
  }
}
```

### Common MCP Servers

```json
// Memory operations
{"matcher": "mcp__memory__.*"}

// GitHub operations
{"matcher": "mcp__github__.*"}

// Linear operations
{"matcher": "mcp__linear__.*"}

// Filesystem operations
{"matcher": "mcp__filesystem__.*"}

// All MCP tools
{"matcher": "mcp__.*__.*"}
```

### MCP Hook Example

```bash
#!/usr/bin/env bash
# Log MCP operations
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
SERVER=$(echo "$TOOL_NAME" | cut -d'_' -f3)
OPERATION=$(echo "$TOOL_NAME" | cut -d'_' -f4-)

echo "[$(date -Iseconds)] MCP $SERVER: $OPERATION" >> "$CLAUDE_PROJECT_DIR/.claude/mcp-operations.log"
exit 0
```

## Plugin Hooks

### Plugin Hook Configuration

Location: `plugin/hooks/hooks.json` or inline in `plugin.json`

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{
          "type": "command",
          "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format-code.sh",
          "timeout": 30
        }]
      }
    ]
  }
}
```

### Plugin-Specific Variables

Use `${CLAUDE_PLUGIN_ROOT}` for plugin paths:

```json
{
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/helper.sh"
}
```

### Plugin Hook Best Practices

**1. Use relative paths with variable:**

```json
{
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
}
```

**2. Include dependencies in plugin:**

```
plugin/
├── .claude-plugin/
│   └── plugin.json
├── hooks/
│   └── hooks.json
└── scripts/
    ├── process.sh
    └── utils.sh
```

**3. Document hook requirements:**

```json
{
  "name": "my-plugin",
  "description": "Plugin with auto-formatting",
  "requirements": {
    "binaries": ["jq", "black"],
    "notes": "PostToolUse hooks require black for Python formatting"
  }
}
```

## Advanced Patterns

### Conditional Execution

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only format during work hours
HOUR=$(date +%H)
if [[ $HOUR -lt 9 || $HOUR -gt 17 ]]; then
  echo "Skipping format outside work hours"
  exit 0
fi

# Only format if file is in src/
if [[ ! "$FILE_PATH" =~ ^.*/src/ ]]; then
  exit 0
fi

# Format the file
black "$FILE_PATH"
```

### Async Operations

```bash
#!/usr/bin/env bash
# Run expensive operation in background

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Start background job
(
  sleep 2
  expensive-operation "$FILE_PATH"
  echo "Background operation completed" >> /tmp/claude-bg.log
) &

# Return immediately
echo "Background operation started"
exit 0
```

### State Management

```bash
#!/usr/bin/env bash
# Track state across hooks

STATE_FILE="/tmp/claude-state.json"

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# Load state
if [[ -f "$STATE_FILE" ]]; then
  STATE=$(cat "$STATE_FILE")
else
  STATE='{"operations": []}'
fi

# Update state
STATE=$(echo "$STATE" | jq ".operations += [\"$TOOL_NAME\"]")
echo "$STATE" > "$STATE_FILE"

# Report
COUNT=$(echo "$STATE" | jq '.operations | length')
echo "Total operations: $COUNT"
exit 0
```

### Multi-File Operations

```bash
#!/usr/bin/env bash
# Update related files

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# If component updated, update index
if [[ "$FILE_PATH" =~ /components/.*\.tsx$ ]]; then
  INDEX_FILE="$(dirname "$FILE_PATH")/index.ts"

  # Regenerate index
  echo "// Auto-generated by hook" > "$INDEX_FILE"
  for file in "$(dirname "$FILE_PATH")"/*.tsx; do
    NAME=$(basename "$file" .tsx)
    echo "export { $NAME } from './$NAME';" >> "$INDEX_FILE"
  done

  echo "Updated $INDEX_FILE"
fi

exit 0
```

### Notification Integration

```bash
#!/usr/bin/env bash
# Send Slack notification

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only notify for important files
if [[ "$FILE_PATH" =~ /src/core/ ]]; then
  WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"

  if [[ -n "$WEBHOOK_URL" ]]; then
    curl -X POST "$WEBHOOK_URL" \
      -H 'Content-Type: application/json' \
      -d "{\"text\":\"Core file modified: $(basename "$FILE_PATH")\"}" \
      2>/dev/null
  fi
fi

exit 0
```

### Validation Pipeline

```bash
#!/usr/bin/env bash
# Multi-stage validation

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Stage 1: Check for dangerous commands
if echo "$COMMAND" | grep -qE '\brm\s+-rf\s+/|\bmkfs\b|\bdd\s+if='; then
  echo "❌ Dangerous command blocked" >&2
  exit 2
fi

# Stage 2: Check for deprecated commands
if echo "$COMMAND" | grep -qE '\bgrep\b|\bfind\b'; then
  echo "⚠ Consider using rg or fd instead" >&2
fi

# Stage 3: Check for common mistakes
if echo "$COMMAND" | grep -qE 'git\s+push\s+--force'; then
  echo "⚠ Force push detected - use with caution" >&2
fi

exit 0
```

### Performance Monitoring

```bash
#!/usr/bin/env bash
# Track hook performance

START=$(date +%s%N)

# Hook logic here
INPUT=$(cat)
# ... process ...

# Calculate duration
END=$(date +%s%N)
DURATION=$(( (END - START) / 1000000 )) # milliseconds

# Log performance
echo "[$(date -Iseconds)] Hook duration: ${DURATION}ms" >> /tmp/claude-perf.log

exit 0
```

```

### references/examples.md

```markdown
# Hook Examples

Real-world examples of Claude Code event hooks for automation, validation, and workflow enhancement.

## Table of Contents

1. [Auto-Formatting](#auto-formatting)
2. [Validation Hooks](#validation-hooks)
3. [CI/CD Integration](#cicd-integration)
4. [Notification Systems](#notification-systems)
5. [Context Injection](#context-injection)
6. [Security Enforcement](#security-enforcement)
7. [Multi-Hook Workflows](#multi-hook-workflows)
8. [Team Collaboration](#team-collaboration)
9. [MCP Integration](#mcp-integration)
10. [Advanced Patterns](#advanced-patterns)
11. [Prompt-Based Hooks](#prompt-based-hooks)
12. [Community Examples](#community-examples)
13. [Component-Scoped Hooks](#component-scoped-hooks)

## Auto-Formatting

### TypeScript with Biome

Auto-format TypeScript files after writing or editing.

**Configuration** (`.claude/settings.json`):

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts|*.tsx)",
        "hooks": [
          {
            "type": "command",
            "command": "biome check --write \"$file\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

**Result**: Every TypeScript file is automatically formatted with Biome after Claude writes or edits it.

### Python with Black

Auto-format Python files with Black.

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "black \"$file\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

**Advanced version with multiple formatters**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "black \"$file\"",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "isort \"$file\"",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

### Rust with rustfmt

Auto-format Rust code.

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.rs)",
        "hooks": [
          {
            "type": "command",
            "command": "rustfmt \"$file\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

### Multi-Language Formatter

Format multiple languages with appropriate tools.

**Script** (`.claude/hooks/format-code.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -z "$FILE_PATH" ]]; then
  exit 0
fi

# Determine formatter based on extension
case "$FILE_PATH" in
  *.ts|*.tsx|*.js|*.jsx)
    if command -v biome &>/dev/null; then
      biome check --write "$FILE_PATH" 2>&1 || true
    fi
    ;;
  *.py)
    if command -v black &>/dev/null; then
      black "$FILE_PATH" 2>&1 || true
      isort "$FILE_PATH" 2>&1 || true
    fi
    ;;
  *.rs)
    if command -v rustfmt &>/dev/null; then
      rustfmt "$FILE_PATH" 2>&1 || true
    fi
    ;;
  *.go)
    if command -v gofmt &>/dev/null; then
      gofmt -w "$FILE_PATH" 2>&1 || true
    fi
    ;;
  *.md)
    if command -v prettier &>/dev/null; then
      prettier --write "$FILE_PATH" 2>&1 || true
    fi
    ;;
esac

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh",
            "timeout": 15
          }
        ]
      }
    ]
  }
}
```

## Validation Hooks

### Bash Command Safety

Validate bash commands before execution.

**Script** (`.claude/hooks/validate-bash.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Only validate Bash tool
if [[ "$TOOL_NAME" != "Bash" ]] || [[ -z "$COMMAND" ]]; then
  exit 0
fi

# Dangerous patterns to block
DANGEROUS_PATTERNS=(
  '\brm\s+-rf\s+/'
  '\bmkfs\b'
  '\bdd\s+if='
  '\bformat\s+[cC]:'
  '>\s*/dev/sd[a-z]'
  '\b:()\{\s*:\|\:&\s*\};:'  # Fork bomb
  '\bchmod\s+777\s+/'
  '\bchown\s+.*\s+/'
)

# Check for dangerous patterns
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$pattern"; then
    cat << EOF >&2
❌ Dangerous command blocked

Command: $COMMAND
Pattern: $pattern

This command could cause system damage and has been blocked.
EOF
    exit 2
  fi
done

# Deprecated command warnings
if echo "$COMMAND" | grep -qE '\bgrep\b'; then
  echo "⚠ Consider using 'rg' (ripgrep) instead of 'grep'" >&2
fi

if echo "$COMMAND" | grep -qE '\bfind\s+\S+\s+-name'; then
  echo "⚠ Consider using 'rg --files' or 'fd' instead of 'find'" >&2
fi

# Force push warning
if echo "$COMMAND" | grep -qE 'git\s+push\s+(--force|-f)'; then
  echo "⚠ Warning: Force push detected. Verify this is intentional." >&2
fi

echo "✓ Command validation passed"
exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

### File Path Security

Prevent path traversal and sensitive file access.

**Script** (`.claude/hooks/validate-paths.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -z "$FILE_PATH" ]]; then
  exit 0
fi

# Check for path traversal
if echo "$FILE_PATH" | grep -qE '\.\./'; then
  cat << EOF >&2
❌ Path traversal detected

File: $FILE_PATH

Paths containing '..' are not allowed for security reasons.
EOF
  exit 2
fi

# Check for sensitive system paths
SENSITIVE_PATTERNS=(
  '^/etc/'
  '^/root/'
  '^/home/[^/]+/\.ssh/'
  '^/var/log/'
  '^/sys/'
  '^/proc/'
)

for pattern in "${SENSITIVE_PATTERNS[@]}"; do
  if echo "$FILE_PATH" | grep -qE "$pattern"; then
    cat << EOF >&2
❌ Access to sensitive system path blocked

File: $FILE_PATH

This path is restricted for security reasons.
EOF
    exit 2
  fi
done

# Check for sensitive files
SENSITIVE_FILES=(
  '\.env$'
  '\.env\.'
  'id_rsa'
  'id_ed25519'
  '\.pem$'
  'credentials'
  'password'
  '\.key$'
  'secret'
)

for pattern in "${SENSITIVE_FILES[@]}"; do
  if echo "$FILE_PATH" | grep -qiE "$pattern"; then
    cat << EOF >&2
⚠ Warning: Accessing sensitive file

File: $FILE_PATH

This file may contain sensitive information. Ensure this is intentional.
EOF
    # Don't block, just warn
  fi
done

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-paths.sh",
            "timeout": 3
          }
        ]
      }
    ]
  }
}
```

### JSON Schema Validation

Validate JSON files against schemas.

**Script** (`.claude/hooks/validate-json.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')

# Only validate JSON files
if [[ ! "$FILE_PATH" =~ \.json$ ]] || [[ -z "$CONTENT" ]]; then
  exit 0
fi

# Validate JSON syntax
if ! echo "$CONTENT" | jq empty 2>/dev/null; then
  echo "❌ Invalid JSON syntax" >&2
  exit 2
fi

# Validate specific files against schemas
case "$FILE_PATH" in
  *package.json)
    # Validate package.json has required fields
    if ! echo "$CONTENT" | jq -e '.name and .version' >/dev/null 2>&1; then
      echo "⚠ package.json missing required fields (name, version)" >&2
    fi
    ;;
  *tsconfig.json)
    # Validate tsconfig has compilerOptions
    if ! echo "$CONTENT" | jq -e '.compilerOptions' >/dev/null 2>&1; then
      echo "⚠ tsconfig.json missing compilerOptions" >&2
    fi
    ;;
  *.claude/settings.json)
    # Validate hooks structure if present
    if echo "$CONTENT" | jq -e '.hooks' >/dev/null 2>&1; then
      # Check each hook has required fields
      if ! echo "$CONTENT" | jq -e '.hooks | to_entries[] | .value[] | .matcher and .hooks' >/dev/null 2>&1; then
        echo "❌ Invalid hooks configuration" >&2
        exit 2
      fi
    fi
    ;;
esac

echo "✓ JSON validation passed"
exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write(*.json)",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-json.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

## CI/CD Integration

### Trigger Build on File Change

Trigger build when specific files are modified.

**Script** (`.claude/hooks/trigger-build.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only trigger for source files
if [[ ! "$FILE_PATH" =~ (src/|lib/|pages/) ]]; then
  exit 0
fi

# Check if CI/CD is configured
if [[ ! -f ".github/workflows/build.yml" ]] && [[ ! -f ".gitlab-ci.yml" ]]; then
  exit 0
fi

# Create marker file for build trigger
MARKER_FILE=".build-needed"
echo "Build triggered by: $FILE_PATH" >> "$MARKER_FILE"

echo "✓ Build marker created: $MARKER_FILE"
echo "Run 'git add $MARKER_FILE && git commit' to trigger CI/CD build"

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/trigger-build.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

### Run Tests After Code Changes

Automatically run tests when code changes.

**Script** (`.claude/hooks/run-tests.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only run tests for source files
if [[ ! "$FILE_PATH" =~ \.(ts|tsx|js|jsx|py|rs)$ ]]; then
  exit 0
fi

# Skip test files themselves
if [[ "$FILE_PATH" =~ \.(test|spec)\. ]]; then
  exit 0
fi

echo "Running tests..."

# Detect test runner and run tests
if [[ -f "package.json" ]] && grep -q '"test"' package.json; then
  # Node.js project
  if command -v bun &>/dev/null; then
    bun test 2>&1 || {
      echo "⚠ Tests failed. Review failures before committing." >&2
      exit 0  # Don't block, just warn
    }
  elif command -v npm &>/dev/null; then
    npm test 2>&1 || {
      echo "⚠ Tests failed. Review failures before committing." >&2
      exit 0
    }
  fi
elif [[ -f "Cargo.toml" ]]; then
  # Rust project
  cargo test 2>&1 || {
    echo "⚠ Tests failed. Review failures before committing." >&2
    exit 0
  }
elif [[ -f "pytest.ini" ]] || [[ -f "pyproject.toml" ]]; then
  # Python project
  pytest 2>&1 || {
    echo "⚠ Tests failed. Review failures before committing." >&2
    exit 0
  }
fi

echo "✓ Tests passed"
exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts|*.tsx|*.py|*.rs)",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-tests.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
```

### Update Documentation

Auto-update docs when code changes.

**Script** (`.claude/hooks/update-docs.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only for public API files
if [[ ! "$FILE_PATH" =~ src/(index|api|public)\. ]]; then
  exit 0
fi

# Generate TypeScript docs
if [[ -f "tsconfig.json" ]] && command -v typedoc &>/dev/null; then
  echo "Generating TypeScript documentation..."
  typedoc --out docs/api src/index.ts 2>&1 || true
fi

# Generate Python docs
if [[ "$FILE_PATH" =~ \.py$ ]] && command -v pdoc &>/dev/null; then
  echo "Generating Python documentation..."
  pdoc --html --force --output-dir docs/api . 2>&1 || true
fi

# Generate Rust docs
if [[ "$FILE_PATH" =~ \.rs$ ]] && [[ -f "Cargo.toml" ]]; then
  echo "Generating Rust documentation..."
  cargo doc --no-deps 2>&1 || true
fi

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/update-docs.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

## Notification Systems

### Slack Integration

Send notifications to Slack.

**Script** (`.claude/hooks/notify-slack.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Load webhook URL from .env if present (safe single-variable extraction)
if [[ -f ".env" ]]; then
  SLACK_WEBHOOK_URL=$(grep -E '^SLACK_WEBHOOK_URL=' .env | cut -d'=' -f2- | tr -d '"' | tr -d "'")
fi

WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"

if [[ -z "$WEBHOOK_URL" ]]; then
  exit 0
fi

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only notify for important operations
case "$TOOL_NAME" in
  Write|Edit)
    # Only notify for specific directories
    if [[ "$FILE_PATH" =~ (src/core/|src/api/|migrations/) ]]; then
      MESSAGE="🤖 Claude modified: \`$(basename "$FILE_PATH")\` in \`$(dirname "$FILE_PATH")\`"

      curl -X POST "$WEBHOOK_URL" \
        -H 'Content-Type: application/json' \
        -d "{\"text\":\"$MESSAGE\"}" \
        --silent --show-error 2>&1 || true
    fi
    ;;
esac

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify-slack.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

### Email Notifications

Send email for important events.

> **Note**: Configure a valid email address before use. The `mail` command will
> succeed even if the email is undeliverable, so verify your mail server setup.

**Script** (`.claude/hooks/send-email.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')

# Only email for session events
if [[ "$HOOK_EVENT" != "SessionEnd" ]]; then
  exit 0
fi

REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Check if email is configured
if ! command -v mail &>/dev/null; then
  exit 0
fi

# Send email
mail -s "Claude Code Session Ended: $REASON" [email protected] << EOF
Session ID: $SESSION_ID
Reason: $REASON
Timestamp: $(date -Iseconds)

This is an automated notification from Claude Code.
EOF

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/send-email.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

### Logging System

Comprehensive logging of all operations.

**Script** (`.claude/hooks/log-operations.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="$CLAUDE_PROJECT_DIR/.claude/logs"
mkdir -p "$LOG_DIR"

INPUT=$(cat)
TIMESTAMP=$(date -Iseconds)
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "N/A"')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Log to daily file
LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).log"

# Create log entry
LOG_ENTRY=$(jq -n \
  --arg ts "$TIMESTAMP" \
  --arg event "$HOOK_EVENT" \
  --arg tool "$TOOL_NAME" \
  --arg session "$SESSION_ID" \
  '{timestamp: $ts, event: $event, tool: $tool, session: $session}')

echo "$LOG_ENTRY" >> "$LOG_FILE"

# Rotate logs older than 30 days
find "$LOG_DIR" -name "*.log" -mtime +30 -delete 2>/dev/null || true

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/log-operations.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

## Context Injection

### Add Timestamp

Add current timestamp to every prompt.

**Script** (`.claude/hooks/add-timestamp.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Output current time context
cat << EOF
Current timestamp: $(date -Iseconds)
Current time: $(date '+%Y-%m-%d %H:%M:%S %Z')
Day of week: $(date '+%A')
EOF

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/add-timestamp.sh",
            "timeout": 1
          }
        ]
      }
    ]
  }
}
```

### Add Git Context

Inject git status into prompt context.

**Script** (`.claude/hooks/git-context.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Check if git repo
if ! git rev-parse --git-dir &>/dev/null; then
  exit 0
fi

cat << EOF
## Git Context

Branch: $(git branch --show-current 2>/dev/null || echo "detached")
Status: $(git status --short 2>/dev/null | wc -l | xargs) files modified
Last commit: $(git log -1 --oneline 2>/dev/null || echo "none")
Remote: $(git remote -v 2>/dev/null | head -1 | awk '{print $2}' || echo "none")
EOF

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/git-context.sh",
            "timeout": 3
          }
        ]
      }
    ]
  }
}
```

### Add Environment Info

Inject environment and system information.

**Script** (`.claude/hooks/env-context.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

cat << EOF
## Environment Context

OS: $(uname -s)
Architecture: $(uname -m)
Node: $(node --version 2>/dev/null || echo "not installed")
Bun: $(bun --version 2>/dev/null || echo "not installed")
Python: $(python3 --version 2>/dev/null || echo "not installed")
Rust: $(rustc --version 2>/dev/null || echo "not installed")

Working directory: $PWD
User: $USER
Shell: $SHELL
EOF

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/env-context.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

## Security Enforcement

### Block Sensitive File Operations

Prevent operations on sensitive files.

**Script** (`.claude/hooks/block-sensitive.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -z "$FILE_PATH" ]]; then
  exit 0
fi

# Define sensitive patterns
BLOCKED_PATTERNS=(
  '\.env$'
  '\.env\.'
  'credentials'
  'secrets'
  'id_rsa'
  'id_ed25519'
  '\.pem$'
  '\.key$'
  '\.p12$'
  'password'
  'token'
  '\.git/config$'
)

# Check against patterns
for pattern in "${BLOCKED_PATTERNS[@]}"; do
  if echo "$FILE_PATH" | grep -qiE "$pattern"; then
    cat << EOF >&2
❌ Access to sensitive file blocked

File: $FILE_PATH
Pattern: $pattern

This file may contain sensitive information and is protected.
If you need to modify this file, do it manually outside Claude Code.
EOF
    exit 2
  fi
done

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-sensitive.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

### Enforce File Permissions

Ensure proper file permissions.

**Script** (`.claude/hooks/enforce-permissions.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -z "$FILE_PATH" ]] || [[ ! -f "$FILE_PATH" ]]; then
  exit 0
fi

# Ensure no world-writable files
if [[ -w "$FILE_PATH" ]] && [[ $(stat -f %A "$FILE_PATH" 2>/dev/null || stat -c %a "$FILE_PATH" 2>/dev/null) =~ [0-9][0-9]2$ ]]; then
  chmod o-w "$FILE_PATH"
  echo "⚠ Removed world-write permission from $FILE_PATH" >&2
fi

# Ensure scripts are executable
if [[ "$FILE_PATH" =~ \.(sh|bash|zsh)$ ]]; then
  if [[ ! -x "$FILE_PATH" ]]; then
    chmod +x "$FILE_PATH"
    echo "✓ Made script executable: $FILE_PATH"
  fi
fi

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-permissions.sh",
            "timeout": 3
          }
        ]
      }
    ]
  }
}
```

### Audit Trail

Create audit trail of all operations.

**Script** (`.claude/hooks/audit-trail.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

AUDIT_FILE="$CLAUDE_PROJECT_DIR/.claude/audit.log"

INPUT=$(cat)
TIMESTAMP=$(date -Iseconds)
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "N/A"')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // "N/A"')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Create audit entry
AUDIT_ENTRY=$(jq -n \
  --arg ts "$TIMESTAMP" \
  --arg event "$HOOK_EVENT" \
  --arg tool "$TOOL_NAME" \
  --arg file "$FILE_PATH" \
  --arg session "$SESSION_ID" \
  --arg user "$USER" \
  --arg host "$HOSTNAME" \
  '{
    timestamp: $ts,
    event: $event,
    tool: $tool,
    file: $file,
    session: $session,
    user: $user,
    host: $host
  }')

# Append to audit log
echo "$AUDIT_ENTRY" >> "$AUDIT_FILE"

# Keep only last 10000 lines
tail -n 10000 "$AUDIT_FILE" > "$AUDIT_FILE.tmp" && mv "$AUDIT_FILE.tmp" "$AUDIT_FILE"

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/audit-trail.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

## Multi-Hook Workflows

### Complete TypeScript Workflow

Format, type-check, lint, and test TypeScript files.

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts|*.tsx)",
        "hooks": [
          {
            "type": "command",
            "command": "biome check --write \"$file\"",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "tsc --noEmit \"$file\"",
            "timeout": 15
          },
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-tests.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

### Python Development Workflow

Format, type-check, lint, and test Python files.

**Configuration**:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "black \"$file\"",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "isort \"$file\"",
            "timeout": 5
          },
          {
            "type": "command",
            "command": "mypy \"$file\"",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "pylint \"$file\"",
            "timeout": 15
          }
        ]
      }
    ]
  }
}
```

### Pre-Commit Workflow

Validate before allowing write operations.

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-paths.sh",
            "timeout": 3
          },
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-sensitive.sh",
            "timeout": 2
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh",
            "timeout": 15
          },
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/audit-trail.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

## Team Collaboration

### Shared Team Hooks

Team-wide formatting and validation.

**Project** (`.claude/settings.json`):

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit(*.ts|*.tsx)",
        "hooks": [
          {
            "type": "command",
            "command": "biome check --write \"$file\"",
            "timeout": 10
          }
        ]
      },
      {
        "matcher": "Write|Edit(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "black \"$file\"",
            "timeout": 10
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

### Personal Overrides

Personal preferences that extend team hooks.

**Personal** (`~/.claude/settings.json`):

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo '👋 Welcome back!' && git status",
            "timeout": 3
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "echo '✅ Task completed at $(date +%H:%M)'",
            "timeout": 1
          }
        ]
      }
    ]
  }
}
```

## MCP Integration

### Log Memory Operations

Track MCP memory tool usage.

**Script** (`.claude/hooks/log-memory.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# Only for memory MCP tools
if [[ ! "$TOOL_NAME" =~ ^mcp__memory__ ]]; then
  exit 0
fi

OPERATION=$(echo "$TOOL_NAME" | sed 's/mcp__memory__//')
TIMESTAMP=$(date -Iseconds)

# Log the operation
echo "[$TIMESTAMP] Memory operation: $OPERATION" >> "$CLAUDE_PROJECT_DIR/.claude/memory-ops.log"

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/log-memory.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

### Validate GitHub Operations

Validate GitHub MCP operations.

**Script** (`.claude/hooks/validate-github.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# Only for GitHub MCP tools
if [[ ! "$TOOL_NAME" =~ ^mcp__github__ ]]; then
  exit 0
fi

# Warn about destructive operations
if [[ "$TOOL_NAME" =~ (delete|close|merge) ]]; then
  echo "⚠ Warning: Destructive GitHub operation: $TOOL_NAME" >&2
fi

exit 0
```

**Configuration**:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__github__.*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-github.sh",
            "timeout": 2
          }
        ]
      }
    ]
  }
}
```

## Advanced Patterns

### Conditional Hook Execution

Execute hooks only under certain conditions.

**Script** (`.claude/hooks/conditional-format.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only format during work hours (9 AM - 5 PM)
HOUR=$(date +%H)
if [[ $HOUR -lt 9 || $HOUR -gt 17 ]]; then
  echo "Skipping format outside work hours"
  exit 0
fi

# Only format files in src/ directory
if [[ ! "$FILE_PATH" =~ ^.*/src/ ]]; then
  exit 0
fi

# Run formatter
if [[ "$FILE_PATH" =~ \.ts$ ]]; then
  biome check --write "$FILE_PATH"
fi

exit 0
```

### State Management

Track state across hook invocations.

**Script** (`.claude/hooks/track-changes.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE="$CLAUDE_PROJECT_DIR/.claude/hook-state.json"

# Initialize state if needed
if [[ ! -f "$STATE_FILE" ]]; then
  echo '{"files_modified": [], "total_operations": 0}' > "$STATE_FILE"
fi

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -n "$FILE_PATH" ]]; then
  # Update state
  STATE=$(cat "$STATE_FILE")
  STATE=$(echo "$STATE" | jq \
    --arg file "$FILE_PATH" \
    '.files_modified += [$file] | .files_modified |= unique | .total_operations += 1')
  echo "$STATE" > "$STATE_FILE"

  # Report
  TOTAL=$(echo "$STATE" | jq '.total_operations')
  echo "Total operations this session: $TOTAL"
fi

exit 0
```

### Async Background Operations

Run expensive operations asynchronously.

**Script** (`.claude/hooks/async-index.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Start background indexing
(
  sleep 1
  # Rebuild search index
  if command -v rg &>/dev/null; then
    rg --files > "$CLAUDE_PROJECT_DIR/.claude/file-index.txt" 2>/dev/null
  fi
  echo "Index updated: $(date -Iseconds)" >> "$CLAUDE_PROJECT_DIR/.claude/index.log"
) &

echo "✓ Background indexing started"
exit 0
```

### Performance Monitoring

Track hook performance.

**Script** (`.claude/hooks/perf-monitor.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

START_NS=$(date +%s%N)

# Original hook logic
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# ... process ...

# Calculate duration
END_NS=$(date +%s%N)
DURATION_MS=$(( (END_NS - START_NS) / 1000000 ))

# Log performance
PERF_LOG="$CLAUDE_PROJECT_DIR/.claude/perf.log"
echo "$(date -Iseconds) | $TOOL_NAME | ${DURATION_MS}ms" >> "$PERF_LOG"

# Warn if slow
if [[ $DURATION_MS -gt 1000 ]]; then
  echo "⚠ Hook took ${DURATION_MS}ms (>1s)" >&2
fi

exit 0
```

### Error Recovery

Robust error handling with recovery.

**Script** (`.claude/hooks/robust-format.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Trap errors
trap 'echo "Error on line $LINENO" >&2; exit 1' ERR

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ -z "$FILE_PATH" ]]; then
  exit 0
fi

# Create backup before formatting
BACKUP="${FILE_PATH}.bak"
cp "$FILE_PATH" "$BACKUP"

# Try to format
if ! biome check --write "$FILE_PATH" 2>/dev/null; then
  # Restore backup on failure
  mv "$BACKUP" "$FILE_PATH"
  echo "⚠ Format failed, restored original file" >&2
  exit 1
fi

# Remove backup on success
rm -f "$BACKUP"
echo "✓ Formatted successfully"
exit 0
```

## Prompt-Based Hooks

Prompt-based hooks use LLM reasoning for context-aware validation. Recommended for complex decisions.

### Smart Security Validation

Use LLM to analyze file operations:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{
          "type": "prompt",
          "prompt": "Analyze this file operation for security issues:\n\n$TOOL_INPUT\n\nCheck for:\n1. Sensitive paths (/etc, ~/.ssh, .env files)\n2. Credentials or API keys in content\n3. Path traversal attempts (..)\n4. Executable file creation\n\nRespond with JSON: {\"decision\": \"allow|deny\", \"reason\": \"brief explanation\"}",
          "timeout": 30
        }]
      }
    ]
  }
}
```

### Context-Aware Bash Validation

Evaluate command safety with reasoning:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "prompt",
          "prompt": "Evaluate if this bash command is safe to execute:\n\n$TOOL_INPUT\n\nConsider:\n1. Could it delete important files?\n2. Could it expose secrets?\n3. Could it modify system configuration?\n4. Is it appropriate for a development environment?\n\nRespond: {\"decision\": \"allow|deny\", \"reason\": \"...\"}",
          "timeout": 30
        }]
      }
    ]
  }
}
```

### Task Completion Verification

Verify work quality before stopping:

```json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "*",
        "hooks": [{
          "type": "prompt",
          "prompt": "Review the completed task. Consider:\n1. Were all requirements addressed?\n2. Were tests added or updated?\n3. Is there any unfinished work?\n4. Should the user be informed of anything?\n\nProvide a brief summary if there are concerns.",
          "timeout": 30
        }]
      }
    ]
  }
}
```

## Community Examples

Real-world examples from the Claude Code community.

### disler/claude-code-hooks-mastery

Comprehensive hook examples using Python with UV for dependency management.

**Directory structure**:
```
.claude/
├── hooks/
│   ├── user_prompt_submit.py   # Prompt validation and logging
│   ├── pre_tool_use.py         # Command blocking
│   ├── post_tool_use.py        # Tool completion logging
│   ├── notification.py         # TTS notifications
│   ├── stop.py                 # AI completion messages
│   ├── subagent_stop.py        # Subagent tracking
│   ├── pre_compact.py          # Transcript backup
│   └── session_start.py        # Context loading
└── settings.json
```

**Configuration pattern**:
```json
{
  "UserPromptSubmit": [{
    "hooks": [{
      "type": "command",
      "command": "uv run .claude/hooks/user_prompt_submit.py --log-only"
    }]
  }],
  "PreToolUse": [{
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "command": "uv run .claude/hooks/pre_tool_use.py"
    }]
  }]
}
```

**Source**: https://github.com/disler/claude-code-hooks-mastery

### ChrisWiles/claude-code-showcase

Complete Claude Code configuration with hooks, skills, agents, and GitHub Actions.

**Features**:
- Auto-format code on file changes
- Run tests when test files change
- Type-check TypeScript
- Block edits on main branch
- Skill matching for prompts

**Branch protection hook**:
```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "[ \"$(git branch --show-current)\" != \"main\" ] || exit 2",
        "timeout": 5
      }]
    }]
  }
}
```

**Source**: https://github.com/ChrisWiles/claude-code-showcase

### GitButler Integration

GitButler provides hooks for automatic branch and commit management.

**Configuration**:
```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "but claude pre-tool",
        "timeout": 5
      }]
    }],
    "PostToolUse": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "but claude post-tool",
        "timeout": 5
      }]
    }],
    "Stop": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "but claude stop",
        "timeout": 10
      }]
    }]
  }
}
```

**Source**: https://docs.gitbutler.com/features/ai-integration/claude-code-hooks

## Component-Scoped Hooks

Hooks defined in skills, agents, and commands frontmatter. Only active when the component is loaded.

### Skill with Validation Hook

```yaml
---
name: secure-coding
description: Security-focused coding skill
hooks:
  PreToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: prompt
          prompt: "Validate this code change for security best practices..."
  PostToolUse:
    - matcher: "Write|Edit(*.ts)"
      hooks:
        - type: command
          command: "eslint --fix \"$file\""
---

# Secure Coding Skill

When active, this skill validates all code changes for security issues.
```

### Agent with Completion Hook

```yaml
---
name: code-reviewer
description: Reviews code for quality issues
model: sonnet
hooks:
  Stop:
    - matcher: "*"
      hooks:
        - type: prompt
          prompt: "Summarize the code review findings and severity levels."
---

# Code Reviewer Agent

Performs thorough code review with summarized findings.
```

### Command with Context Hook

```yaml
---
description: Deploy to staging environment
argument-hint: [component to deploy]
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./.claude/hooks/validate-deploy.sh"
---

# Deploy Command

Deploys the specified component to staging with pre-flight checks.
```

## External Resources

- [Official Hooks Reference](https://code.claude.com/docs/en/hooks)
- [Hooks Guide](https://code.claude.com/docs/en/hooks-guide)
- [Claude Code Blog: Hook Configuration](https://claude.com/blog/how-to-configure-hooks)
- [disler/claude-code-hooks-mastery](https://github.com/disler/claude-code-hooks-mastery)
- [ChrisWiles/claude-code-showcase](https://github.com/ChrisWiles/claude-code-showcase)
- [GitButler Hooks Documentation](https://docs.gitbutler.com/features/ai-integration/claude-code-hooks)

```

claude-hook-authoring | SkillHub