Transform repository into a plugin marketplace structure with two plugins: - claude-code plugin: Complete toolkit with 5 skills * claude-code-plugins * claude-code-slash-commands * claude-code-hooks * claude-code-subagents * claude-code-memory - claude-skills plugin: Meta-skill for creating Agent Skills * Comprehensive best practices guide * Templates and examples * Progressive disclosure patterns Infrastructure: - Add marketplace.json manifest - Create plugin.json for each plugin - Update documentation for marketplace structure - Add contribution and testing guides Installation: - /plugin install claude-code@claude-skills - /plugin install claude-skills@claude-skills 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
18 KiB
name, description
| name | description |
|---|---|
| Claude Code Hooks | Configure event-driven hooks for Claude Code to automate workflows, validate code, inject context, and control tool execution. Use when setting up automation, enforcing standards, or integrating external tools with Claude Code. |
Claude Code Hooks
When to Use This Skill
Use this skill when:
- Automating workflows with event-driven triggers
- Enforcing code standards or security policies
- Validating changes before/after tool execution
- Injecting context at session start
- Logging or monitoring tool usage
- Controlling MCP tool permissions
- Setting up team-wide automation
Do NOT use this skill for:
- Creating slash commands (use claude-code-slash-commands skill)
- Building full plugins (use claude-code-plugins skill)
- One-time scripts (just run them directly)
Quick Start: Creating a Hook
1. Basic Hook Configuration
Create or edit settings file:
# Project-wide (team shared)
.claude/settings.json
# User-specific (not shared)
.claude/settings.local.json
# Global (all projects)
~/.claude/settings.json
2. Simple Example - Log File Changes
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "echo \"$(date): Modified $TOOL_ARGS\" >> .claude/changes.log"
}
]
}
]
}
}
Result: Every file write/edit gets logged with timestamp.
Hook Types
SessionStart
Runs when a session begins (before any interaction):
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo 'Project: MyApp | Env: production' > .claude/context.txt"
}
]
}
]
}
}
Use For: Injecting project context, loading environment info
SessionEnd
Runs when a session ends:
{
"hooks": {
"SessionEnd": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "git add . && git stash save 'Claude session backup'"
}
]
}
]
}
}
Use For: Cleanup, backups, state preservation
PreToolUse
Runs after Claude creates tool parameters but before execution:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'About to write: $TOOL_ARGS' && exit 0"
}
]
}
]
}
}
Use For: Validation, permission checks, preprocessing
PostToolUse
Runs immediately after tool completes successfully:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS"
}
]
}
]
}
}
Use For: Formatting, linting, post-processing
UserPromptSubmit
Runs when user submits a prompt (before Claude processes it):
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "date >> .claude/prompt-log.txt && exit 0"
}
]
}
]
}
}
Use For: Logging, analytics, context injection
Notification
Runs when Claude sends notifications (permission requests, idle waiting):
{
"hooks": {
"Notification": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude needs attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Use For: Desktop notifications, alerts
Stop / SubagentStop
Runs when agents finish responding:
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo 'Response complete' >> .claude/activity.log && exit 0"
}
]
}
]
}
}
Use For: Post-response processing, logging
PreCompact
Runs before context compaction:
{
"hooks": {
"PreCompact": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo 'Compacting context...' && exit 0"
}
]
}
]
}
}
Use For: Logging, saving state before compaction
Matcher Patterns
Tool Name Matching
// Single tool
"matcher": "Write"
// Multiple tools (OR)
"matcher": "Write|Edit|Read"
// All tools
"matcher": "*"
// Regex patterns supported
"matcher": "Bash.*|Git.*"
Common Patterns
// File operations
"matcher": "Write|Edit|Delete"
// Read operations
"matcher": "Read|Grep|Glob"
// Git operations
"matcher": "Bash(git:*)"
// All Bash commands
"matcher": "Bash"
Hook Configuration
Basic Structure
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
Command Field
Use bash commands with environment variables:
{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && npm test"
}
Timeout Field
Optional execution limit in seconds (default: 60):
{
"type": "command",
"command": "long-running-task",
"timeout": 300 // 5 minutes
}
Exit Codes & Control Flow
Exit Code 0 - Success (Continue)
# Hook succeeds, stdout shown to user (most events)
echo "Success message"
exit 0
Exit Code 2 - Blocking Error
# Hook blocks operation, stderr sent to Claude
echo "Error: Cannot proceed" >&2
exit 2
Use For: Validation failures, permission denials
Other Exit Codes - Non-Blocking Error
# Hook fails but operation continues
echo "Warning: Hook failed" >&2
exit 1
Environment Variables
Available Variables
$CLAUDE_PROJECT_DIR # Current project directory
$TOOL_NAME # Name of the tool being used
$TOOL_ARGS # Arguments passed to the tool
$HOOK_EVENT # Event type (PreToolUse, PostToolUse, etc.)
Example Usage
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && git add $TOOL_ARGS"
}
]
}
]
}
}
Complete Examples
1. Auto-Format on Save
Format files automatically after writing/editing:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && npx prettier --write $TOOL_ARGS && exit 0"
}
]
}
]
}
}
2. Security Validation
Block writes containing secrets:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if grep -qiE '(password|api[_-]?key|secret)' $TOOL_ARGS 2>/dev/null; then echo 'Error: Possible secret detected' >&2; exit 2; fi; exit 0",
"timeout": 30
}
]
}
]
}
}
3. Auto-Git on Changes
Automatically stage and commit changes:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && git add $TOOL_ARGS && git commit -m 'Auto-commit: Modified $TOOL_ARGS' && exit 0"
}
]
}
]
}
}
4. Inject Project Context
Load project info at session start:
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "cat << EOF\nProject: MyApp\nEnvironment: Production\nKey Files: src/config.js, .env.example\nCoding Standards: See CONTRIBUTING.md\nEOF"
}
]
}
]
}
}
5. Test Before Commit
Run tests before allowing file writes:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && npm test -- --silent || (echo 'Tests failed, cannot save' >&2; exit 2)",
"timeout": 120
}
]
}
]
}
}
6. Log All Activity
Track all tool usage:
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') - $TOOL_NAME - $TOOL_ARGS\" >> $CLAUDE_PROJECT_DIR/.claude/activity.log && exit 0"
}
]
}
]
}
}
7. Enforce File Permissions
Ensure proper permissions on sensitive files:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if [[ $TOOL_ARGS == *\".env\"* ]]; then chmod 600 $TOOL_ARGS; fi; exit 0"
}
]
}
]
}
}
8. Notify on Long Sessions
Alert when session ends:
{
"hooks": {
"SessionEnd": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Session ended\" with title \"Claude Code\"' && exit 0"
}
]
}
]
}
}
Advanced Patterns
Multiple Hooks per Event
Run several hooks in sequence:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS"
},
{
"type": "command",
"command": "npx eslint --fix $TOOL_ARGS"
},
{
"type": "command",
"command": "git add $TOOL_ARGS"
}
]
}
]
}
}
Hooks run in order; if one fails, subsequent hooks may not run.
Conditional Execution
Use bash conditionals:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if [[ $TOOL_ARGS == *.js ]]; then npm run format $TOOL_ARGS; fi; exit 0"
}
]
}
]
}
}
Script-Based Hooks
Call external scripts for complex logic:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-write.sh $TOOL_ARGS",
"timeout": 60
}
]
}
]
}
}
validate-write.sh:
#!/bin/bash
file=$1
# Check file size
if [ -f "$file" ] && [ $(wc -c < "$file") -gt 1000000 ]; then
echo "Error: File too large" >&2
exit 2
fi
# Check for secrets
if grep -qiE '(password|api[_-]?key)' "$file" 2>/dev/null; then
echo "Error: Possible secret detected" >&2
exit 2
fi
exit 0
JSON Output for Advanced Control
Return structured data (advanced):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '{\"continue\": true, \"decision\": \"allow\"}'"
}
]
}
]
}
}
Settings File Hierarchy
Load Order (Highest to Lowest Priority)
.claude/settings.local.json- Project-specific, user-only (gitignored).claude/settings.json- Project-specific, team-shared (in git)~/.claude/settings.json- Global, user-only
Use Cases
Global (~/.claude/settings.json):
- Personal preferences
- Universal logging
- Desktop notifications
Project (.claude/settings.json):
- Team standards
- Project-specific automation
- Shared workflows
Local (.claude/settings.local.json):
- Personal overrides
- Development experiments
- User-specific credentials
Testing Hooks
1. Start Simple
Test with basic echo:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'Hook triggered for: $TOOL_ARGS' && exit 0"
}
]
}
]
}
}
2. Verify Environment Variables
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"Project: $CLAUDE_PROJECT_DIR\" && exit 0"
}
]
}
]
}
}
3. Test Exit Codes
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'This should block' >&2 && exit 2"
}
]
}
]
}
}
Try writing a file - should be blocked.
4. Check Timeout
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "sleep 5 && echo 'Done'",
"timeout": 3
}
]
}
]
}
}
Should timeout after 3 seconds.
Common Use Cases
Code Quality Enforcement
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS && npx eslint --fix $TOOL_ARGS && exit 0"
}
]
}
]
}
}
Security Scanning
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if grep -qE '(sk-[a-zA-Z0-9]{48}|ghp_[a-zA-Z0-9]{36})' $TOOL_ARGS 2>/dev/null; then echo 'API key detected!' >&2; exit 2; fi; exit 0"
}
]
}
]
}
}
Automatic Documentation
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if [[ $TOOL_ARGS == *.js ]]; then npx jsdoc2md $TOOL_ARGS >> API.md; fi; exit 0"
}
]
}
]
}
}
Change Tracking
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"$(date): $TOOL_NAME $TOOL_ARGS\" >> .claude/audit.log && exit 0"
}
]
}
]
}
}
Troubleshooting
Hook Not Triggering
Issue: Hook doesn't run when expected
Solutions:
- Check matcher pattern matches tool name
- Verify settings file location and syntax (valid JSON)
- Restart Claude Code to reload settings
- Test with simple echo command first
Hook Fails Silently
Issue: Hook runs but doesn't work as expected
Solutions:
- Check exit codes (0 for success)
- Verify command syntax in terminal first
- Check timeout isn't too short
- Look at stderr output
Command Not Found
Issue: Hook says command not found
Solutions:
- Use full path to command:
/usr/local/bin/prettier - Check command is in PATH
- Test command in terminal first
Permission Denied
Issue: Hook can't execute script
Solutions:
chmod +x .claude/hooks/script.sh
Best Practices
1. Always Exit with Code
# Good - explicit exit
command && exit 0
# Bad - implicit exit might vary
command
2. Use Timeouts for Long Operations
{
"type": "command",
"command": "npm test",
"timeout": 120 // Don't let tests run forever
}
3. Handle Errors Gracefully
# Check if file exists before processing
if [ -f "$TOOL_ARGS" ]; then
process_file "$TOOL_ARGS"
else
echo "File not found" >&2
fi
exit 0 # Don't block on missing file
4. Use Scripts for Complex Logic
Don't put complex bash in JSON:
// Bad
{
"command": "if [ -f $TOOL_ARGS ]; then cat $TOOL_ARGS | grep pattern | wc -l; fi && exit 0"
}
// Good
{
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-file.sh $TOOL_ARGS"
}
5. Test Before Deploying
Test in .claude/settings.local.json before adding to .claude/settings.json.
6. Document Your Hooks
Add comments in settings:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $TOOL_ARGS",
"_comment": "Auto-format all JS/TS files on save"
}
]
}
]
}
}
(Note: JSON doesn't officially support comments, but Claude Code ignores _comment fields)
Security Considerations
Validate Input
# Sanitize TOOL_ARGS before use
if [[ ! $TOOL_ARGS =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
echo "Invalid file path" >&2
exit 2
fi
Limit Permissions
Don't use * matcher unless necessary:
// Specific
"matcher": "Write|Edit"
// Too broad
"matcher": "*"
Avoid Destructive Commands
# Dangerous
rm -rf $TOOL_ARGS
# Better - add safety checks
if [[ -f "$TOOL_ARGS" ]] && [[ "$TOOL_ARGS" != "/" ]]; then
rm "$TOOL_ARGS"
fi
Use Local Settings for Sensitive Operations
Keep potentially dangerous hooks in .claude/settings.local.json (not shared).
Resources
- Official Hooks Documentation
- Slash Commands - For command-based automation
- Plugin Development - For packaging hooks in plugins
Remember: Hooks are powerful automation tools. Start with simple, safe operations. Test thoroughly before deploying to teams. Use exit codes properly to control flow.