From all-skills
Guides configuring event-driven hooks for Claude Code to run shell commands on tool calls, lifecycle events, and user prompts. Enables custom validations, formatting, integrations, and workflows.
npx claudepluginhub vinnie357/claude-skills --plugin alliumThis skill uses the workspace's default tool permissions.
Guide for creating hooks that execute shell commands or scripts in response to Claude Code events and tool calls.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
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}}"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: