Guide for creating event-driven hooks for Claude Code. Use when automating responses to tool calls, lifecycle events, or implementing custom validations.
Creates event-driven hooks that execute shell commands in response to Claude Code tool calls and lifecycle events.
npx claudepluginhub vinnie357/claude-skillsThis skill is limited to using the following tools:
templates/plugin-hook.mdtemplates/skill-hook.mdGuide for creating hooks that execute shell commands or scripts in response to Claude Code events and tool calls.
Activate this skill when:
Hooks are shell commands that execute automatically in response to specific events:
Hooks are configured in:
<plugin-root>/.claude-plugin/hooks.json.claude/hooks.jsonplugin.jsonStandalone hooks.json:
{
"onToolCall": {
"Write": {
"before": ["./hooks/format-check.sh"],
"after": ["./hooks/lint.sh"]
},
"Bash": {
"before": ["./hooks/validate-command.sh"]
}
},
"onInstall": ["./hooks/setup.sh"],
"onUninstall": ["./hooks/cleanup.sh"],
"onUserPromptSubmit": ["./hooks/log-prompt.sh"]
}
Inline in plugin.json:
{
"hooks": {
"onToolCall": {
"Write": {
"after": ["prettier --write {{file_path}}"]
}
}
}
}
Execute before or after specific tool calls.
Available Tools:
Read, Write, Edit, MultiEditBash, BashOutputGlob, GrepTask, Skill, SlashCommandTodoWriteWebFetch, WebSearchAskUserQuestionExample:
{
"onToolCall": {
"Write": {
"before": [
"echo 'Writing file: {{file_path}}'",
"./hooks/backup.sh {{file_path}}"
],
"after": [
"prettier --write {{file_path}}",
"git add {{file_path}}"
]
},
"Edit": {
"after": ["eslint --fix {{file_path}}"]
}
}
}
Execute during plugin installation/uninstallation.
{
"onInstall": [
"./hooks/setup-dependencies.sh",
"npm install",
"echo 'Plugin installed successfully'"
],
"onUninstall": [
"./hooks/cleanup.sh",
"echo 'Plugin uninstalled'"
]
}
Execute when user submits a prompt:
{
"onUserPromptSubmit": [
"./hooks/log-interaction.sh '{{prompt}}'",
"./hooks/check-context.sh"
]
}
Hooks have access to context-specific variables using {{variable}} syntax.
Different tools provide different variables:
Write Tool:
{{file_path}}: Path to file being written{{content}}: Content being written (before hooks only)Edit Tool:
{{file_path}}: Path to file being edited{{old_string}}: String being replaced{{new_string}}: Replacement stringBash Tool:
{{command}}: Command being executedRead Tool:
{{file_path}}: Path to file being readAvailable in all hooks:
{{cwd}}: Current working directory{{timestamp}}: Current Unix timestamp{{user}}: Current user{{plugin_root}}: Plugin installation directory{{prompt}}: User's submitted prompt text{
"onToolCall": {
"Write": {
"after": [
"prettier --write {{file_path}}",
"eslint --fix {{file_path}}"
]
}
}
}
{
"onToolCall": {
"Bash": {
"before": ["./hooks/validate-git-command.sh '{{command}}'"]
}
}
}
validate-git-command.sh:
#!/bin/bash
COMMAND="$1"
# Block force push to main/master
if [[ "$COMMAND" =~ "git push --force" ]] && [[ "$COMMAND" =~ "main|master" ]]; then
echo "ERROR: Force push to main/master is not allowed"
exit 1
fi
exit 0
{
"onToolCall": {
"Write": {
"before": ["cp {{file_path}} {{file_path}}.backup"]
},
"Edit": {
"before": ["cp {{file_path}} {{file_path}}.backup"]
}
}
}
{
"onToolCall": {
"Write": {
"after": ["./hooks/log-file-change.sh {{file_path}}"]
}
},
"onUserPromptSubmit": ["./hooks/log-prompt.sh '{{prompt}}'"]
}
log-file-change.sh:
#!/bin/bash
FILE="$1"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "$TIMESTAMP - Modified: $FILE" >> .claude/file-changes.log
{
"onToolCall": {
"Write": {
"after": [
"notify-send 'File Updated' 'Modified {{file_path}}'",
"curl -X POST https://api.example.com/notify -d 'file={{file_path}}'"
]
}
}
}
Multiple hooks execute in array order:
{
"onToolCall": {
"Write": {
"after": [
"echo 'Step 1'", // Runs first
"echo 'Step 2'", // Runs second
"echo 'Step 3'" // Runs third
]
}
}
}
Before Hooks:
0: Continue with tool executionAfter Hooks:
#!/bin/bash
# Before hook - blocks tool on error
if [[ ! -f "$1" ]]; then
echo "ERROR: File does not exist"
exit 1 # Blocks tool execution
fi
# Validation passed
exit 0
Hooks block execution - keep them lightweight:
{
"onToolCall": {
"Write": {
// ✅ Fast linter
"after": ["eslint --fix {{file_path}}"]
// ❌ Slow test suite
// "after": ["npm test"]
}
}
}
Reference scripts with paths relative to plugin:
{
"onInstall": ["${CLAUDE_PLUGIN_ROOT}/hooks/setup.sh"]
}
Always validate hook variables:
#!/bin/bash
FILE="$1"
if [[ -z "$FILE" ]]; then
echo "ERROR: No file path provided"
exit 1
fi
if [[ ! -f "$FILE" ]]; then
echo "ERROR: File does not exist: $FILE"
exit 1
fi
#!/bin/bash
echo "Running pre-commit checks..."
if ! npm run lint; then
echo "❌ Linting failed. Please fix errors before committing."
exit 1
fi
echo "✅ All checks passed"
exit 0
#!/bin/bash
# Handle files with spaces in names
FILE="$1"
# Validate file type
if [[ ! "$FILE" =~ \.(js|ts|jsx|tsx)$ ]]; then
# Skip non-JavaScript files silently
exit 0
fi
# Run formatter
prettier --write "$FILE"
Before hooks can block dangerous operations:
{
"onToolCall": {
"Bash": {
"before": ["./hooks/validate-command.sh '{{command}}'"]
}
}
}
validate-command.sh:
#!/bin/bash
COMMAND="$1"
# Block dangerous patterns
DANGEROUS_PATTERNS=(
"rm -rf /"
"dd if="
"mkfs"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" =~ $pattern ]]; then
echo "ERROR: Dangerous command blocked: $pattern"
exit 1
fi
done
exit 0
Only hook necessary tools:
{
// ✅ Specific tools only
"onToolCall": {
"Write": { "after": ["./format.sh {{file_path}}"] }
}
// ❌ Don't hook everything unnecessarily
}
#!/bin/bash
# Sanitize file path
FILE=$(realpath "$1")
# Ensure file is within project
if [[ ! "$FILE" =~ ^$(pwd) ]]; then
echo "ERROR: File outside project directory"
exit 1
fi
{
"onToolCall": {
"Write": {
"before": ["set -x; ./hooks/debug.sh {{file_path}}; set +x"]
}
}
}
#!/bin/bash
LOG_FILE=".claude/hooks.log"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "$TIMESTAMP - Hook: $0, Args: $@" >> "$LOG_FILE"
# Rest of hook logic...
# Test hook with sample data
./hooks/format.sh "src/main.js"
# Check exit code
echo $?
{
"onToolCall": {
"Write": {
"after": [
"prettier --write {{file_path}}",
"eslint --fix {{file_path}}"
]
},
"Edit": {
"after": [
"prettier --write {{file_path}}",
"eslint --fix {{file_path}}"
]
}
}
}
{
"onToolCall": {
"Write": {
"after": ["./hooks/run-relevant-tests.sh {{file_path}}"]
}
}
}
{
"onToolCall": {
"Write": {
"after": ["git add {{file_path}}"]
},
"Edit": {
"after": ["git add {{file_path}}"]
}
}
}
chmod +x hooks/script.sh{{file_path}} not {{filepath}}"{{file_path}}"Reference templates for common hook configurations:
claude-hooks/
└── templates/
├── plugin-hook.md # Plugin hook configuration example
└── skill-hook.md # Skill/subagent frontmatter hooks example
Example configuration for defining hooks in a plugin's hooks/hooks.json:
Write|Edit matcher${CLAUDE_PLUGIN_ROOT} for script referencesExample frontmatter for embedding hooks directly in skills:
For more information:
This skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.
UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples.