Guide for creating hooks that trigger actions in response to Claude Code events
Execute shell commands automatically when Claude Code events occur, like before/after tool calls or plugin lifecycle events. Use it to add custom validation, auto-formatting, or external integrations triggered by specific actions.
/plugin marketplace add vinnie357/claude-skills/plugin install claude-code@vinnie357This skill is limited to using the following tools:
Guide 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}}"For more information:
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.