public/SKILLS/Development & Code Tools/claude-starter/claude-hook-builder/SKILL.md
Interactive hook creator for Claude Code. Triggers when user mentions creating hooks, PreToolUse, PostToolUse, hook validation, hook configuration, settings.json hooks, or wants to automate tool execution workflows.
npx skillsauth add eric861129/skills_all-in-one claude-hook-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guide users through creating effective Claude Code hooks for tool validation, automation, and workflow enhancement. Auto-invokes when users want to create or configure hooks.
Auto-invoke when users mention:
.claude/skills/ai/claude-code/docs/code_claude_com/docs_en_hooks.md.claude/skills/ai/claude-code/docs/code_claude_com/docs_en_hooks-guide.md.claude/docs/creating-components.mdAsk the user:
Let me help you create a Claude Code hook! I need some details:
1. **What should this hook do?**
Examples:
- Auto-format code after editing files
- Validate bash commands before execution
- Add context when user submits prompts
- Prevent access to sensitive files
- Run tests after file changes
2. **When should it trigger?**
- PreToolUse (before tool execution)
- PostToolUse (after tool execution)
- UserPromptSubmit (when user sends message)
- Stop (when Claude finishes responding)
- SubagentStop (when subagent finishes)
- SessionStart (when session begins)
- SessionEnd (when session ends)
- Notification (when notification sent)
- PermissionRequest (when permission requested)
3. **Which tools should it match?**
- Specific tool (Write, Edit, Bash, Read, etc.)
- Multiple tools (Write|Edit)
- All tools (*)
- MCP tools (mcp__server__tool)
4. **What should it return?**
- Simple exit code (0 = success, 2 = block)
- JSON with decision control
- Additional context for Claude
- Modified tool inputs
5. **Scope:**
- User-level (`~/.claude/settings.json`)
- Project-level (`.claude/settings.json`)
- Local project (`.claude/settings.local.json`)
Bash Command Hook:
{
"type": "command",
"command": "/path/to/script.sh"
}
Prompt-based Hook:
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS"
}
Runs before tool executes.
Use for:
JSON Output:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow" | "deny" | "ask",
"permissionDecisionReason": "Why this decision",
"updatedInput": {
"field": "new value"
}
}
}
Runs after tool completes.
Use for:
JSON Output:
{
"decision": "block" | undefined,
"reason": "Why blocking",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Extra info for Claude"
}
}
Runs when user submits prompt.
Use for:
JSON Output:
{
"decision": "block" | undefined,
"reason": "Why blocking",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Extra context"
}
}
Runs when Claude/subagent finishes.
Use for:
JSON Output:
{
"decision": "block" | undefined,
"reason": "Why must continue"
}
Runs when session starts.
Use for:
JSON Output:
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Initial context"
}
}
Special: Can persist environment variables:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
fi
Runs when session ends.
Use for:
For bash command hooks, create a script:
Template:
#!/usr/bin/env bash
# Read JSON input from stdin
INPUT=$(cat)
# Parse JSON (requires jq)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Your validation logic here
if [[ condition ]]; then
echo "Error message" >&2
exit 2 # Block operation
fi
# Success
exit 0
Python Template:
#!/usr/bin/env python3
import json
import sys
# Read JSON input
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
# Your logic here
if condition:
# Block with error
print("Error message", file=sys.stderr)
sys.exit(2)
# Or return JSON for control
output = {
"decision": "approve",
"reason": "Auto-approved"
}
print(json.dumps(output))
sys.exit(0)
Add hook configuration:
Basic Hook:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
}
]
}
]
}
}
Multiple Hooks:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh",
"timeout": 30
},
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/lint.sh",
"timeout": 60
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
}
]
}
]
}
}
No Matcher (events without tools):
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/add-context.sh"
}
]
}
]
}
}
Each event receives JSON on stdin:
Common fields:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/dir",
"permission_mode": "default",
"hook_event_name": "PostToolUse"
}
PreToolUse/PostToolUse:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": { /* PostToolUse only */
"success": true
}
}
UserPromptSubmit:
{
"prompt": "User's submitted message"
}
Stop/SubagentStop:
{
"stop_hook_active": false
}
0: Success
2: Blocking error
Other: Non-blocking warning
Test script directly:
# Create test input
echo '{
"tool_name": "Write",
"tool_input": {
"file_path": "test.txt",
"content": "hello"
}
}' | .claude/hooks/your-hook.sh
# Check exit code
echo $?
Test in Claude Code:
1. Add hook to settings.json
2. Restart Claude Code
3. Run /hooks to verify it's loaded
4. Trigger the hook (e.g., write a file)
5. Check verbose mode (Ctrl+O) for output
Debug mode:
claude --debug
# Shows hook execution details
Show the complete configuration:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh",
"timeout": 60
}
]
}
]
}
}
Hook script (.claude/hooks/format-python.sh):
#!/usr/bin/env bash
INPUT=$(cat)
# Get file path
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Only process .py files
if [[ "$FILE_PATH" == *.py ]]; then
# Run black formatter
python -m black "$FILE_PATH" 2>&1
if [[ $? -eq 0 ]]; then
echo "Formatted: $FILE_PATH" >&2
fi
fi
exit 0
Configuration:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-python.sh"
}
]
}
]
}
}
Hook script (.claude/hooks/validate-bash.py):
#!/usr/bin/env python3
import json
import sys
import re
# Dangerous patterns
DANGEROUS = [
(r'\brm\s+-rf\s+/', 'Dangerous: rm -rf on root'),
(r'>\s*/dev/sd[a-z]', 'Dangerous: writing to block device'),
(r'\bcurl\s+.*\|\s*bash', 'Dangerous: piping curl to bash'),
]
try:
data = json.load(sys.stdin)
except:
sys.exit(1)
if data.get('tool_name') != 'Bash':
sys.exit(0)
command = data.get('tool_input', {}).get('command', '')
# Check for dangerous patterns
for pattern, message in DANGEROUS:
if re.search(pattern, command):
print(f"⚠️ {message}", file=sys.stderr)
print(f"Command: {command}", file=sys.stderr)
sys.exit(2) # Block
sys.exit(0) # Allow
Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
}
]
}
]
}
}
Hook script (.claude/hooks/add-timestamp.sh):
#!/usr/bin/env bash
# Output current timestamp
echo "Current time: $(date '+%Y-%m-%d %H:%M:%S %Z')"
exit 0
Configuration:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/add-timestamp.sh"
}
]
}
]
}
}
Hook script (.claude/hooks/auto-approve-docs.py):
#!/usr/bin/env python3
import json
import sys
data = json.load(sys.stdin)
if data.get('tool_name') != 'Read':
sys.exit(0)
file_path = data.get('tool_input', {}).get('file_path', '')
# Auto-approve docs
if any(file_path.endswith(ext) for ext in ['.md', '.txt', '.json', '.yaml']):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Documentation file auto-approved"
},
"suppressOutput": True
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0)
Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/auto-approve-docs.py"
}
]
}
]
}
}
Hook script (.claude/hooks/block-secrets.sh):
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Block sensitive files
if [[ "$FILE_PATH" =~ \.env ||
"$FILE_PATH" =~ secrets/ ||
"$FILE_PATH" =~ \.aws/ ]]; then
echo "⛔ Access to sensitive file blocked: $FILE_PATH" >&2
exit 2
fi
exit 0
Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-secrets.sh"
}
]
}
]
}
}
Configuration:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate whether Claude should stop. Context: $ARGUMENTS\n\nCheck if:\n1. All tasks are complete\n2. Tests are passing\n3. No errors need addressing\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}",
"timeout": 30
}
]
}
]
}
}
Hook script (.claude/hooks/session-setup.sh):
#!/usr/bin/env bash
# Set up environment for session
if [ -n "$CLAUDE_ENV_FILE" ]; then
# Load nvm
source ~/.nvm/nvm.sh
nvm use 20
# Capture environment changes
export -p >> "$CLAUDE_ENV_FILE"
# Add custom variables
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
# Add context
echo "Development environment initialized"
echo "Node version: $(node --version)"
exit 0
Configuration:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-setup.sh"
}
]
}
]
}
}
Exact match:
"matcher": "Write"
Multiple tools (regex):
"matcher": "Write|Edit|NotebookEdit"
All tools:
"matcher": "*"
Or:
"matcher": ""
MCP tools:
"matcher": "mcp__github__.*"
"matcher": "mcp__.*__write.*"
Event-specific matchers:
Notification:
"matcher": "permission_prompt"
"matcher": "idle_prompt"
PreCompact:
"matcher": "manual"
"matcher": "auto"
SessionStart:
"matcher": "startup"
"matcher": "resume"
"matcher": "clear"
Available in hook scripts:
$CLAUDE_PROJECT_DIR - Absolute path to project root$CLAUDE_CODE_REMOTE - "true" if remote/web, empty if local$CLAUDE_ENV_FILE - (SessionStart only) File to persist env vars✅ Keep hooks fast (<100ms recommended)
✅ Provide clear error messages
✅ Use appropriate exit codes
✅ Quote variables in bash: "$VAR"
✅ Validate inputs before processing
✅ Test thoroughly before deploying
✅ Use $CLAUDE_PROJECT_DIR for portability
✅ Document what your hook does
❌ Run slow operations (full test suites) ❌ Block legitimate operations unnecessarily ❌ Use hooks for everything (be selective) ❌ Forget to handle errors ❌ Skip input validation ❌ Hardcode absolute paths ❌ Leave debug output in production
⚠️ USE AT YOUR OWN RISK
Hooks execute arbitrary commands:
Best practices:
..)Check:
settings.json correctlychmod +x script.sh#!/usr/bin/env bashDebug:
# Run with debug mode
claude --debug
# Check hook execution in output
# Shows: "Executing hooks for PostToolUse:Write"
Check:
echo '{}' | ./script.shecho $?./script.sh | jq .View errors:
claude --debugCheck:
# Make script executable
chmod +x .claude/hooks/script.sh
# Verify permissions
ls -la .claude/hooks/
Validate JSON:
# Test JSON output
echo '{}' | ./script.sh | jq .
# Common issues:
# - Missing quotes
# - Trailing commas
# - Single quotes instead of double
.claude/skills/ai/claude-code/docs/code_claude_com/docs_en_hooks.md.claude/skills/ai/claude-code/docs/code_claude_com/docs_en_hooks-guide.md.claude/skills/ai/claude-code/docs/code_claude_com/docs_en_settings.md.claude/docs/creating-components.mddevelopment
Run structured What-If scenario analysis with multi-branch possibility exploration. Use this skill when the user asks speculative questions like "what if...", "what would happen if...", "what are the possibilities", "explore scenarios", "scenario analysis", "possibility space", "what could go wrong", "best case / worst case", "risk analysis", "contingency planning", "strategic options", or any question about uncertain futures. Also trigger when the user faces a fork-in-the-road decision, wants to stress-test an idea, or needs to think through consequences before committing.
development
Access comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
development
Use when challenging ideas, plans, decisions, or proposals using structured critical reasoning. Invoke to play devil's advocate, run a pre-mortem, red team, or audit evidence and assumptions.
tools
Core skill for the deep research and writing tool. Write scientific manuscripts in full paragraphs (never bullet points). Use two-stage process with (1) section outlines with key points using research-lookup then (2) convert to flowing prose. IMRAD structure, citations (APA/AMA/Vancouver), figures/tables, reporting guidelines (CONSORT/STROBE/PRISMA), for research papers and journal submissions.