with one click
my-skill
// Adds a deterministic hook that fires automatically on tool events. Use when asked to enforce a rule on every commit, block a dangerous pattern, validate output, or gate any tool call.
// Adds a deterministic hook that fires automatically on tool events. Use when asked to enforce a rule on every commit, block a dangerous pattern, validate output, or gate any tool call.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | add_hook |
| description | Adds a deterministic hook that fires automatically on tool events. Use when asked to enforce a rule on every commit, block a dangerous pattern, validate output, or gate any tool call. |
| when_to_use | Use when the user wants to automate a check or action that runs every time a specific event occurs — e.g. "block pushes to main", "run linter before every commit", "log every Bash command". |
| argument-hint | <hook-name> <event> <description> |
| allowed-tools | Read Write Bash |
Pick the event that matches when the hook should fire:
Tool execution (most common):
PreToolUse — before a tool runs; output block JSON to block itPostToolUse — after a tool runs; cannot block, can log or validatePostToolBatch — after a batch of parallel tool calls completesPermissionRequest — when Claude asks for permission; can auto-approveSession lifecycle:
SessionStart — when a session beginsStop — when Claude stops generating (end of turn)StopFailure — when a turn failsUser input:
UserPromptSubmit — when the user submits a promptUserPromptExpansion — when slash commands expandAgent/task events:
SubagentStart / SubagentStop — subagent lifecycleTaskCreated / TaskCompleted — task tracking eventsFile/config events:
FileChanged, CwdChanged, ConfigChangeCreate .claude/hooks/<name>.sh:
#!/usr/bin/env bash
# Hook receives JSON on stdin from Claude Code.
INPUT=$(cat)
# exit 0 = allow (or no-op for non-blocking events)
# stdout JSON with {"decision":"block","reason":"..."} = block (PreToolUse)
# stdout JSON with {"additionalContext":"..."} = inject context
# Always exit 0 — exit 1 means hook error, not a block
# Example: block pushes to main
CMD=$(echo "$INPUT" | python3 -c "
import json, sys
try:
data = json.load(sys.stdin)
except Exception:
sys.exit(0)
print((data.get('tool_input', {}) or {}).get('command', '') or '')
" 2>/dev/null || true)
[ -z "$CMD" ] && exit 0
if echo "$CMD" | grep -qE 'git push.*(main|master)'; then
python3 -c "import json,sys; print(json.dumps({'decision':'block','reason':sys.argv[1]}))" \
"Direct push to main is not allowed. Use a branch and PR."
exit 0
fi
exit 0
Make it executable: chmod +x .claude/hooks/<name>.sh
Add to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/<name>.sh", "timeout": 10}]
}
]
}
}
Matchers: tool name (Bash, Write, Edit), pipe-separated (Write|Edit), or * for all tools.
Hooks can also live in a skill's frontmatter and fire only while the skill is active:
---
name: my-skill
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./.claude/hooks/check.sh"
---
Trigger the wired tool and verify the hook fires. Check $CLAUDE_PROJECT_DIR is set correctly in the command path.