Loading...
Loading...
Guide for creating event-driven hooks for Claude Code. Use when automating responses to tool calls, lifecycle events, or implementing custom validations.
npx skill4agent add vinnie357/claude-skills claude-hooks<plugin-root>/.claude-plugin/hooks.json.claude/hooks.jsonplugin.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"]
}{
"hooks": {
"onToolCall": {
"Write": {
"after": ["prettier --write {{file_path}}"]
}
}
}
}ReadWriteEditMultiEditBashBashOutputGlobGrepTaskSkillSlashCommandTodoWriteWebFetchWebSearchAskUserQuestion{
"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}}"]
}
}
}{
"onInstall": [
"./hooks/setup-dependencies.sh",
"npm install",
"echo 'Plugin installed successfully'"
],
"onUninstall": [
"./hooks/cleanup.sh",
"echo 'Plugin uninstalled'"
]
}{
"onUserPromptSubmit": [
"./hooks/log-interaction.sh '{{prompt}}'",
"./hooks/check-context.sh"
]
}{{variable}}{{file_path}}{{content}}{{file_path}}{{old_string}}{{new_string}}{{command}}{{file_path}}{{cwd}}{{timestamp}}{{user}}{{plugin_root}}{{prompt}}{
"onToolCall": {
"Write": {
"after": [
"prettier --write {{file_path}}",
"eslint --fix {{file_path}}"
]
}
}
}{
"onToolCall": {
"Bash": {
"before": ["./hooks/validate-git-command.sh '{{command}}'"]
}
}
}#!/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}}'"]
}#!/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}}'"
]
}
}
}{
"onToolCall": {
"Write": {
"after": [
"echo 'Step 1'", // Runs first
"echo 'Step 2'", // Runs second
"echo 'Step 3'" // Runs third
]
}
}
}0#!/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{
"onToolCall": {
"Write": {
// ✅ Fast linter
"after": ["eslint --fix {{file_path}}"]
// ❌ Slow test suite
// "after": ["npm test"]
}
}
}{
"onInstall": ["${CLAUDE_PLUGIN_ROOT}/hooks/setup.sh"]
}#!/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"{
"onToolCall": {
"Bash": {
"before": ["./hooks/validate-command.sh '{{command}}'"]
}
}
}#!/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{
// ✅ 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}}{{filepath}}"{{file_path}}"claude-hooks/
└── templates/
├── plugin-hook.md # Plugin hook configuration example
└── skill-hook.md # Skill/subagent frontmatter hooks examplehooks/hooks.jsonWrite|Edit${CLAUDE_PLUGIN_ROOT}