Back to skills
SKILL.md
Hooks Reference
ASecurityUse this skill when asked about "hooks", "PreToolUse", "PostToolUse", "SessionStart", "hook events", "validate tool use", "block commands", "add context on session start", or implementing event-driven automation.
- 14 stars
- 0 votes
- 0 copies
- 2 views
- Added February 7, 2026
Works with
Security analysis
93/100- Performs destructive filesystem operations
npx -y skills add a-ariff/ariff-claude-plugins --skill hooks-reference --agent claude-codeAre you the author of Hooks Reference?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/a-ariff-hooks-reference)---
name: hooks-reference
description: >-
Use this skill when asked about "hooks", "PreToolUse", "PostToolUse",
"SessionStart", "hook events", "validate tool use", "block commands",
"add context on session start", or implementing event-driven automation.
---
# Hooks Reference Skill
This skill provides comprehensive guidance for implementing Claude Code hooks - event handlers that automate validation, context loading, and workflow enforcement.
## Hook Events Overview
| Event | When Triggered | Use Cases |
|-------|----------------|-----------|
| `PreToolUse` | Before tool executes | Validate, block, modify |
| `PostToolUse` | After tool completes | Audit, react, verify |
| `PermissionRequest` | Permission dialog shown | Auto-allow, auto-deny |
| `Stop` | Claude finishes | Force continue, verify |
| `SubagentStop` | Subagent finishes | Verify task complete |
| `SessionStart` | Session begins | Load context, setup |
| `SessionEnd` | Session ends | Cleanup, save state |
| `UserPromptSubmit` | Prompt submitted | Validate, add context |
| `PreCompact` | Before compaction | Preserve info |
| `Notification` | Notification sent | Alert, log |
## hooks.json Structure
Basic structure:
```json
{
"description": "What these hooks do",
"hooks": {
"EventName": [
{
"matcher": "Pattern",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/handler.sh",
"timeout": 30
}
]
}
]
}
}
```
## Matcher Patterns
For tool events (PreToolUse, PostToolUse, PermissionRequest):
- `"Write"` - Match exact tool name
- `"Write|Edit"` - Match multiple tools (regex)
- `"Notebook.*"` - Regex pattern
- `"*"` or `""` - Match all tools
For SessionStart:
- `"startup"` - Initial startup
- `"resume"` - From --resume, --continue, /resume
- `"clear"` - From /clear
- `"compact"` - From auto/manual compact
For Notification:
- `"permission_prompt"` - Permission requests
- `"idle_prompt"` - Claude waiting for input
## Hook Types
### Command Hooks (type: "command")
Execute a bash script:
```json
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
"timeout": 30
}
```
### Prompt Hooks (type: "prompt")
LLM-based evaluation (Stop, SubagentStop only):
```json
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete.",
"timeout": 30
}
```
## Exit Codes
| Exit Code | Meaning | Behavior |
|-----------|---------|----------|
| 0 | Success | Action proceeds, stdout to user (verbose) |
| 2 | Block | Action blocked, stderr shown to Claude |
| Other | Error | Non-blocking, stderr to user (verbose) |
## Hook Input (stdin JSON)
Common fields:
```json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse"
}
```
PreToolUse specific:
```json
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_use_id": "toolu_01ABC..."
}
```
SessionStart specific:
```json
{
"source": "startup"
}
```
Stop specific:
```json
{
"stop_hook_active": false
}
```
## Advanced JSON Output
Return structured decisions via stdout (exit code 0):
### PreToolUse Decision Control
```json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved documentation file",
"updatedInput": {
"field_to_modify": "new value"
}
}
}
```
Decision values: `"allow"`, `"deny"`, `"ask"`
### Stop Decision Control
```json
{
"decision": "block",
"reason": "Not all tasks complete - still need to run tests"
}
```
### UserPromptSubmit Context
```json
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Current time: 2024-01-15 10:30:00"
}
}
```
### SessionStart Context
```json
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Project context loaded..."
}
}
```
## Example Hooks
### 1. Validate File Writes (PreToolUse)
hooks/hooks.json:
```json
{
"description": "Validate file write operations",
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate-write.sh",
"timeout": 30
}
]
}
]
}
}
```
scripts/validate-write.sh:
```bash
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
# Block writes to sensitive files
if [[ "$FILE_PATH" == *.env* ]] || [[ "$FILE_PATH" == *secret* ]]; then
echo "Cannot write to sensitive files: $FILE_PATH" >&2
exit 2
fi
# Block writes outside project
if [[ ! "$FILE_PATH" == "$CLAUDE_PROJECT_DIR"* ]]; then
echo "Cannot write outside project directory" >&2
exit 2
fi
exit 0
```
### 2. Block Dangerous Commands (PreToolUse)
hooks/hooks.json:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate-bash.sh"
}
]
}
]
}
}
```
scripts/validate-bash.sh:
```bash
#!/usr/bin/env bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Block destructive commands
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
":(){ :|:& };:"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "Blocked dangerous command pattern: $pattern" >&2
exit 2
fi
done
exit 0
```
### 3. Load Project Context (SessionStart)
hooks/hooks.json:
```json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
}
]
}
]
}
}
```
scripts/load-context.sh:
```bash
#!/usr/bin/env bash
CONTEXT=""
# Load CLAUDE.md if exists
if [ -f "$CLAUDE_PROJECT_DIR/CLAUDE.md" ]; then
CONTEXT+="Project instructions from CLAUDE.md have been loaded.\n"
fi
# Add git status
if [ -d "$CLAUDE_PROJECT_DIR/.git" ]; then
BRANCH=$(git -C "$CLAUDE_PROJECT_DIR" branch --show-current 2>/dev/null)
CONTEXT+="Current git branch: $BRANCH\n"
fi
# Output as JSON for structured context
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "$CONTEXT"
}
}
EOF
exit 0
```
### 4. Verify Before Stop (Stop)
Using prompt-based hook:
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS\n\nCheck if:\n1. All requested tasks are complete\n2. No errors need addressing\n3. No tests need running\n\nRespond with: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"
}
]
}
]
}
}
```
### 5. Add Timestamp to Prompts (UserPromptSubmit)
hooks/hooks.json:
```json
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/add-timestamp.sh"
}
]
}
]
}
}
```
scripts/add-timestamp.sh:
```bash
#!/usr/bin/env bash
# Plain text stdout is added as context
echo "Current time: $(date '+%Y-%m-%d %H:%M:%S %Z')"
exit 0
```
## Environment Variables
Available in hooks:
- `${CLAUDE_PLUGIN_ROOT}` - Absolute path to plugin directory
- `$CLAUDE_PROJECT_DIR` - Project root directory
- `$CLAUDE_ENV_FILE` - (SessionStart only) File to persist env vars
- `$CLAUDE_CODE_REMOTE` - "true" if running in web environment
## Persisting Environment (SessionStart)
```bash
#!/usr/bin/env bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
echo 'export API_URL=http://localhost:3000' >> "$CLAUDE_ENV_FILE"
fi
exit 0
```
## Best Practices
1. **Always quote variables**: Use `"$VAR"` not `$VAR`
2. **Validate input**: Never trust stdin blindly
3. **Use portable paths**: `${CLAUDE_PLUGIN_ROOT}` for plugin files
4. **Set timeouts**: Prevent hanging hooks
5. **Handle errors**: Check for missing fields with `// empty`
6. **Keep hooks fast**: Target <1 second execution
7. **Use jq for JSON**: Safer than string parsing
## Debugging
Enable debug mode:
```bash
claude --debug
```
Test hook manually:
```bash
echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | \
./scripts/validate-write.sh
echo $? # Check exit code
```
## References
- Hooks documentation: https://code.claude.com/docs/en/hooks
- Hooks guide: https://code.claude.com/docs/en/hooks-guide
- Example hooks: https://github.com/anthropics/claude-code/tree/main/examples/hooks
Attribution
Comments
Loading comments…