Create effective Claude Code hooks -- shell commands that execute automatically at specific lifecycle points, enabling deterministic control over Claude's behavior.
Scanned 6/6/2026
Install via CLI
openskills install frank-luongt/faos-skills-marketplace<!-- AUTO-GENERATED by export-skills.py — DO NOT EDIT -->
---
name: cc-hooks-creator
description: Create, configure, and debug Claude Code hooks -- shell commands that execute at lifecycle events (PreToolUse, PostToolUse, Stop, SessionStart, etc.). Use when building file protection, code formatting, notifications, context loading, or any automation triggered by Claude Code events. Covers inline commands, Python scripts, JSON output, state management, and security.
tags: [claude-code, hooks, automation, lifecycle]
---
> **Platform Note:** This skill was designed for multi-agent execution. Perplexity Computer handles orchestration automatically — treat sub-agent instructions as sequential steps to complete thoroughly.
# Claude Code Hooks Creator
Create effective Claude Code hooks -- shell commands that execute automatically at specific lifecycle points, enabling deterministic control over Claude's behavior.
## When to Use
- Creating automation before/after tool execution
- File protection (block edits to sensitive files)
- Code formatting after writes/edits
- Loading context at session start
- Notifications and logging
- Debugging or fixing existing hooks
## Hook Events
| Event | When It Runs | Common Use Cases |
|---|---|---|
| `PreToolUse` | Before tool executes | Block operations, validate inputs, auto-approve |
| `PostToolUse` | After tool completes | Format files, log operations, validate output |
| `Stop` | When Claude finishes | Remind to store learnings, validate completion |
| `SubagentStop` | When subagent completes | Validate subagent output |
| `UserPromptSubmit` | When user submits prompt | Add context, validate prompts |
| `SessionStart` | Session begins | Load context, set environment |
| `SessionEnd` | Session ends | Cleanup, logging |
| `PreCompact` | Before context compact | Save important context |
## Hook Creation Workflow
### 1. Understand Requirements
- **What action?** (log, format, block, notify, validate)
- **When?** (before/after tool, on stop, session start)
- **Which tools?** (Bash, Write, Edit, all)
- **What conditions?** (file types, patterns, always)
### 2. Input/Output Format
**Input** (JSON via stdin):
```json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/directory",
"hook_event_name": "EventName",
"tool_name": "Write",
"tool_input": {"file_path": "/path/to/file"}
}
```
**Output** (exit codes):
- Exit 0: Success
- Exit 2: Blocking error (stderr shown to Claude)
- Other: Non-blocking error (stderr logged)
**Advanced JSON output** (exit 0):
```json
{
"decision": "block",
"reason": "Explanation for Claude",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask"
}
}
```
### 3. Implementation
**Inline command (simple):**
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
}]
}]
}
}
```
**Python script (complex):**
```python
#!/usr/bin/env python3
import json, sys
def main():
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
# Your logic here...
sys.exit(0) # Allow
# print("Error message", file=sys.stderr); sys.exit(2) # Block
if __name__ == "__main__":
main()
```
### 4. Configure in Settings
Add to `~/.claude/settings.json` (user) or `.claude/settings.json` (project):
```json
{
"hooks": {
"EventName": [{
"matcher": "ToolPattern",
"hooks": [{
"type": "command",
"command": "/path/to/hook-script.py",
"timeout": 30
}]
}]
}
}
```
**Matcher patterns:** Exact (`"Write"`), Regex (`"Edit|Write"`), All (`"*"` or `""`)
## Common Hook Patterns
### File Protection (PreToolUse)
```python
#!/usr/bin/env python3
import json, sys
PROTECTED = ['.env', 'package-lock.json', '.git/', 'credentials']
input_data = json.load(sys.stdin)
file_path = input_data.get('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)
sys.exit(0)
```
### Code Formatter (PostToolUse)
```python
#!/usr/bin/env python3
import json, sys, subprocess
input_data = json.load(sys.stdin)
file_path = input_data.get('tool_input', {}).get('file_path', '')
if file_path.endswith('.py'):
subprocess.run(['black', file_path], capture_output=True)
elif file_path.endswith(('.ts', '.tsx', '.js', '.jsx')):
subprocess.run(['npx', 'prettier', '--write', file_path], capture_output=True)
sys.exit(0)
```
### Context Loader (SessionStart)
```python
#!/usr/bin/env python3
import json, sys, os
result = os.popen('git log --oneline -5 2>/dev/null').read()
output = {
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": f"Recent commits:\n{result}"
}
}
print(json.dumps(output))
sys.exit(0)
```
## State Management
For hooks tracking state across invocations:
```python
import json, os
from datetime import datetime
STATE_FILE = os.path.expanduser("~/.claude/hook_state.json")
def load_state():
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f)
return {"invocations": 0, "last_run": None}
def save_state(state):
with open(STATE_FILE, 'w') as f:
json.dump(state, f)
```
## Testing and Debugging
1. Make script executable: `chmod +x /path/to/hook.py`
2. Test manually: `echo '{"tool_name":"Write"}' | /path/to/hook.py`
3. Run with debug: `claude --debug`
4. Check verbose output: `Ctrl+O` in Claude Code
## Security
- Hooks run with your user permissions
- Validate and sanitize input data
- Quote shell variables: `"$VAR"` not `$VAR`
- Block path traversal (check for `..`)
- Skip sensitive files (.env, credentials, keys)
- Use absolute paths for scripts
## Anti-Patterns
| Avoid | Why | Instead |
|---|---|---|
| Inline commands for complex logic | Hard to debug, no error handling | Use Python/Bash scripts |
| Missing timeout | Hook can hang indefinitely | Set `"timeout": 30` |
| No exit code handling | Unclear success/failure | Use 0 (allow), 2 (block) |
| Modifying tool input without reason | Confuses Claude | Only modify when necessary, document why |
| Missing `try/except` | JSON parse errors crash hook | Wrap in try/except |
## References
- Source: claude-skills/cc-hooks-creator (MIT License)
- [Claude Code Hooks Documentation](https://docs.anthropic.com/en/docs/claude-code/hooks)
<!-- Source: .faos/custom/skills/tools/cc-hooks-creator/SKILL.md -->
No comments yet. Be the first to comment!