Agent skill
faion-make-hooks-skill
Claude Code hooks expert. Create, debug, and optimize hooks for PreToolUse, PostToolUse, Stop, UserPromptSubmit, SessionStart, and other lifecycle events. Includes templates, patterns, and security best practices.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/faion-make-hooks-skill
SKILL.md
Claude Code Hooks Expert
You are an expert on Claude Code hooks - automated scripts that execute at specific lifecycle events. Help users create, debug, and optimize hooks.
Quick Reference
Hook Events
| Event | Matcher Support | Use Case |
|---|---|---|
| PreToolUse | Yes | Block/allow tools, auto-approve, modify inputs |
| PostToolUse | Yes | Auto-format, lint, log, validate results |
| PermissionRequest | Yes | Programmatic permission handling |
| UserPromptSubmit | No | Validate prompts, add context, block sensitive |
| Stop | No | Prevent exit, continue work |
| SubagentStop | No | Control subagent lifecycle |
| Notification | No | Custom alerts |
| SessionStart | Yes (startup/resume/clear/compact) |
Load context, set env vars |
| SessionEnd | No | Cleanup, logging |
| PreCompact | Yes (manual/auto) |
Pre-compaction actions |
Configuration Locations
~/.claude/settings.json # User-level (all projects)
.claude/settings.json # Project-level (committed)
.claude/settings.local.json # Local project (gitignored)
Component frontmatter # Skills, Agents, Commands
plugins/*/hooks/hooks.json # Plugin hooks
Basic Structure
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}
Exit Codes
| Code | Meaning | Output Handling |
|---|---|---|
| 0 | Success | stdout parsed for JSON or added as context |
| 2 | Blocking error | stderr shown, tool blocked (PreToolUse) |
| Other | Non-blocking error | stderr shown in verbose mode |
Input Schema
Common Fields (All Hooks)
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default|plan|acceptEdits|dontAsk|bypassPermissions",
"hook_event_name": "PreToolUse"
}
Tool-Specific Input
Bash:
{
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run tests",
"timeout": 120000,
"run_in_background": false
}
}
Write:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.ts",
"content": "file content"
}
}
Edit:
{
"tool_name": "Edit",
"tool_input": {
"file_path": "/path/to/file.ts",
"old_string": "original",
"new_string": "replacement",
"replace_all": false
}
}
MCP Tools:
{
"tool_name": "mcp__server__tool_name",
"tool_input": { /* tool-specific */ }
}
Output Schema
PreToolUse Decision
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "Auto-approved: documentation file",
"updatedInput": {
"command": "modified command"
},
"additionalContext": "Context for Claude"
},
"suppressOutput": true
}
Stop/SubagentStop Decision
{
"decision": "block",
"reason": "Tests failed - must continue debugging"
}
UserPromptSubmit Decision
{
"decision": "block",
"reason": "Prompt contains sensitive information"
}
Or just print plain text to stdout for context injection.
Common Fields
{
"continue": true,
"stopReason": "Message when continue=false",
"suppressOutput": false,
"systemMessage": "Warning shown to user"
}
Matcher Patterns
| Pattern | Matches |
|---|---|
"Bash" |
Exact match only |
"Edit|Write" |
Multiple tools (pipe) |
"Edit.*" |
Regex pattern |
"mcp__memory__.*" |
MCP tool pattern |
"*" or "" |
All tools |
Environment Variables
| Variable | Description |
|---|---|
CLAUDE_PROJECT_DIR |
Project root path |
CLAUDE_CODE_REMOTE |
"true" for web, empty for CLI |
CLAUDE_PLUGIN_ROOT |
Plugin directory (plugins only) |
CLAUDE_ENV_FILE |
File to persist env vars (SessionStart only) |
Templates
Python Hook Template
#!/usr/bin/env python3
"""Hook description."""
import json
import sys
def main():
try:
data = json.load(sys.stdin)
except json.JSONDecodeError:
sys.exit(1)
tool_name = data.get("tool_name", "")
tool_input = data.get("tool_input", {})
# Your logic here
# Block with error message
# print("Error message", file=sys.stderr)
# sys.exit(2)
# Allow with JSON output
# output = {"hookSpecificOutput": {...}}
# print(json.dumps(output))
sys.exit(0)
if __name__ == "__main__":
main()
Bash Hook Template
#!/bin/bash
set -euo pipefail
# Read input
INPUT=$(cat)
# Parse with jq
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Your logic here
# Block: print to stderr, exit 2
# echo "Error message" >&2
# exit 2
# Allow
exit 0
Component-Scoped Hook (Skill/Agent frontmatter)
---
name: my-skill
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate.sh"
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "./scripts/format.sh"
once: true # Run only once per session
---
Common Patterns
1. Auto-Approve Safe Operations
if tool_name == "Read" and file_path.endswith((".md", ".txt", ".json")):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Documentation file"
}
}
print(json.dumps(output))
2. Block Dangerous Commands
BLOCKED = ["rm -rf /", "DROP TABLE", "sudo rm"]
if any(pattern in command for pattern in BLOCKED):
print(f"Blocked: {command}", file=sys.stderr)
sys.exit(2)
3. Auto-Format After Edit
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -I {} prettier --write {}"
}
]
}
]
}
}
4. Add Context at Session Start
#!/bin/bash
echo "Project: $(basename \"$CLAUDE_PROJECT_DIR\")"
echo "Branch: $(git branch --show-current 2>/dev/null || echo 'N/A')"
echo "Node: $(node -v 2>/dev/null || echo 'N/A')"
5. Prevent Premature Exit
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. Respond {\"ok\": true} to stop, {\"ok\": false, \"reason\": \"...\"} to continue."
}
]
}
]
}
}
6. Command Logging
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '\"[\\(.hook_event_name)] \\(.tool_input.command)\"' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
7. File Protection
PROTECTED = [".env", "package-lock.json", ".git/", "secrets/"]
file_path = tool_input.get("file_path", "")
if any(p in file_path for p in PROTECTED):
print(f"Protected file: {file_path}", file=sys.stderr)
sys.exit(2)
Security Best Practices
- Quote all variables:
"$VAR"not$VAR - Validate inputs: Check for empty, null, unexpected types
- Block path traversal: Reject paths with
.. - Use absolute paths: For scripts and files
- Skip sensitive files:
.env,.git/, credentials - Test manually first: Before adding to config
- Keep hooks fast: < 60s timeout default
Debugging
# List registered hooks
claude /hooks
# Run with debug output
claude --debug
# Test script manually
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./hook.py
# Check hook linter
.claude/plugins/*/skills/hook-development/scripts/hook-linter.sh ./my-hook.sh
Naming Convention
Faion Network Convention (Global)
For shared/reusable hooks:
Pattern: faion-{event}-{purpose}-hook.{ext}
| Type | Pattern | Example |
|---|---|---|
| Pre-tool | faion-pre-{tool}-{purpose}-hook |
faion-pre-bash-security-hook.py |
| Post-tool | faion-post-{tool}-{purpose}-hook |
faion-post-edit-format-hook.sh |
| Session | faion-session-{phase}-{purpose}-hook |
faion-session-start-context-hook.sh |
| Stop | faion-stop-{purpose}-hook |
faion-stop-validation-hook.py |
Project-Specific Convention (Local)
For project-specific hooks that should NOT be committed to faion-network:
Pattern: {project}-{event}-{purpose}-hook.{ext}
| Example | Description |
|---|---|
myapp-pre-bash-lint-hook.sh |
Lint before bash in myapp |
shopify-post-edit-sync-hook.py |
Sync after edits |
acme-session-start-env-hook.sh |
Load ACME env vars |
Setup:
# Add to .gitignore at the same level as .claude/
echo ".claude/scripts/hooks/{project}-*" >> .gitignore
Attribution (add comment at top of hook):
# Created with faion.net framework
Rules Summary
| Scope | Prefix | Suffix | Gitignore |
|---|---|---|---|
| Global | faion- |
-hook.{ext} |
No |
| Project | {project}- |
-hook.{ext} |
Yes (parent) |
Extensions: .py (Python), .sh (Bash), .js (Node)
Hook Directories
~/.claude/scripts/hooks/
├── faion-pre-bash-security-hook.py # Global
├── faion-post-edit-format-hook.sh # Global
├── myapp-pre-bash-lint-hook.sh # Project (gitignored)
└── myapp-session-start-env-hook.sh # Project (gitignored)
Full structure: docs/directory-structure.md
Related Conventions
- Skills:
faion-{name}-skillor{project}-{name}-skill - Agents:
faion-{name}-agentor{project}-{name}-agent - Commands:
{verb}or{project}-{action}
References
- Official Hooks Documentation
- Load detailed references with
/faion-make-hooks-skill:references/input-schemas.md- Complete input schemasreferences/output-schemas.md- Complete output schemasreferences/templates.md- Ready-to-use templatesreferences/patterns.md- Common patterns and examples
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?