Use when reviewing hooks for safety, timeouts, and correct frontmatter.
Analyzes and validates Claude Code hooks for safety, correct configuration, and best practices.
npx claudepluginhub agent-sh/enhanceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Analyze hook definitions and scripts for safety, correctness, and best practices.
const args = '$ARGUMENTS'.split(' ').filter(Boolean);
const targetPath = args.find(a => !a.startsWith('--')) || '.';
const fix = args.includes('--fix');
Hooks are automated actions triggered at specific points in a Claude Code session. They enable validation, monitoring, and control of Claude's actions through bash commands or LLM-based evaluation.
Hooks fire in this sequence:
| Order | Event | Description | Matcher Required |
|---|---|---|---|
| 1 | SessionStart | Session begins or resumes | No |
| 2 | UserPromptSubmit | User submits a prompt | No |
| 3 | PreToolUse | Before tool execution (can modify/block) | Yes |
| 4 | PermissionRequest | When permission dialog appears | Yes |
| 5 | PostToolUse | After tool succeeds | Yes |
| 6 | SubagentStart | When spawning a subagent | No |
| 7 | SubagentStop | When subagent finishes | No |
| 8 | Stop | Claude finishes responding | No |
| 9 | PreCompact | Before context compaction | No |
| 10 | SessionEnd | Session terminates | No |
| 11 | Notification | Claude Code sends notifications | No |
Command Hooks (type: "command"):
Prompt Hooks (type: "prompt"):
Stop and SubagentStop events| File | Location | Scope | Committed |
|---|---|---|---|
| User settings | ~/.claude/settings.json | All projects | No |
| Project settings | .claude/settings.json | Current project | Yes |
| Local settings | .claude/settings.local.json | Current project | No |
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-bash.sh",
"timeout": 30
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all requested tasks are complete.",
"timeout": 30
}
]
}
]
}
}
| Pattern | Description |
|---|---|
Write | Match exact tool name |
Edit|Write | Match multiple tools (regex OR) |
Notebook.* | Regex pattern matching |
* or "" | Match all tools |
| (omitted) | Required for Stop, SubagentStop, UserPromptSubmit |
All hooks receive this JSON structure:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript",
"cwd": "/project/root",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
}
}
| Exit Code | Behavior |
|---|---|
| 0 | Success - stdout shown to user or added as context |
| 2 | Blocking error - stderr shown, action blocked |
| Other | Non-blocking error - stderr shown in verbose mode |
PreToolUse Decision Control:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "Reason for decision",
"updatedInput": {
"command": "modified command"
},
"additionalContext": "Context for Claude"
}
}
Stop/SubagentStop Control:
{
"decision": "block",
"reason": "Tasks incomplete: missing test coverage"
}
| Variable | Description | Available In |
|---|---|---|
CLAUDE_PROJECT_DIR | Absolute path to project root | All hooks |
CLAUDE_CODE_REMOTE | "true" if remote session | All hooks |
CLAUDE_ENV_FILE | Path to persist env vars | SessionStart only |
CLAUDE_FILE_PATHS | Space-separated file paths | PostToolUse (Write/Edit) |
Security Firewall (PreToolUse):
#!/usr/bin/env bash
set -euo pipefail
cmd=$(jq -r '.tool_input.command // ""')
# Block dangerous patterns
if echo "$cmd" | grep -qE 'rm -rf|git reset --hard|curl.*\|.*sh'; then
echo '{"decision": "block", "reason": "Dangerous command blocked"}' >&2
exit 2
fi
exit 0
Auto-Formatter (PostToolUse):
#!/usr/bin/env bash
set -euo pipefail
files=$(jq -r '.tool_input.file_path // ""')
for file in $files; do
case "$file" in
*.py) black "$file" 2>/dev/null || true ;;
*.js|*.ts) prettier --write "$file" 2>/dev/null || true ;;
esac
done
exit 0
Command Logger (PreToolUse):
#!/usr/bin/env bash
set -euo pipefail
cmd=$(jq -r '.tool_input.command // ""')
printf '%s %s\n' "$(date -Is)" "$cmd" >> .claude/bash-commands.log
exit 0
Workflow Orchestration (SubagentStop - prompt type):
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Review the subagent's work. Did it complete all tasks?"
}
]
}
]
}
}
Required:
--- delimitersname field in frontmatterdescription field in frontmatterRecommended:
timeout for command hooks (default: 30s)Flag:
Required Safety Patterns:
set -euo pipefail at script startDangerous Patterns to Flag:
| Pattern | Risk | Certainty |
|---|---|---|
rm -rf | Destructive without confirmation | HIGH |
git reset --hard | Data loss risk | HIGH |
curl | sh | Remote code execution | HIGH |
eval "$input" | Arbitrary code execution | HIGH |
rm -r | Recursive delete (may be intentional) | MEDIUM |
git push --force | Force push (may be intentional) | MEDIUM |
Check: Scripts use correct exit codes
Flag:
exit 0 for success pathCheck: Hook type matches event
Flag:
| Event | Appropriate Use Cases |
|---|---|
PreToolUse | Security validation, command blocking, input modification |
PostToolUse | Formatting, logging, notifications |
Stop | Completion checks, cleanup, summary |
SubagentStop | Workflow orchestration, result validation |
SessionStart | Environment setup, initialization |
Flag:
Guidelines:
Flag:
PreToolUse Output Fields:
permissionDecision: allow, deny, or askpermissionDecisionReason: Explanation for decisionupdatedInput: Modified tool input (optional)additionalContext: Context for Claude (optional)Flag:
Check: Matcher syntax is valid
Flag:
* without justification)$CLAUDE_PROJECT_DIR)#!/usr/bin/env bash
set -euo pipefail
Add exit 0 at end of script
---
name: hook-name
description: Hook description
timeout: 30
---
Replace exit 1 with exit 2 for blocking errors
## Hook Analysis: {hook-name}
**File**: {path}
**Type**: {command|prompt|config}
**Event**: {PreToolUse|PostToolUse|Stop|...}
### Summary
- HIGH: {count} issues
- MEDIUM: {count} issues
### Frontmatter Issues ({n})
| Issue | Fix | Certainty |
### Safety Issues ({n})
| Issue | Fix | Certainty |
### Exit Code Issues ({n})
| Issue | Fix | Certainty |
### Lifecycle Issues ({n})
| Issue | Fix | Certainty |
### Output Format Issues ({n})
| Issue | Fix | Certainty |
| Category | Patterns | Auto-Fixable |
|---|---|---|
| Frontmatter | 3 | 2 |
| Safety | 6 | 2 |
| Exit Code | 3 | 2 |
| Hook Type | 2 | 0 |
| Lifecycle | 5 | 0 |
| Timeout | 3 | 0 |
| Output | 3 | 0 |
| Matcher | 3 | 0 |
| Anti-Pattern | 5 | 0 |
| Total | 33 | 6 |
<bad_example>
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // ""')
Why it's bad: Missing set -euo pipefail means errors may silently pass.
</bad_example>
<good_example>
#!/usr/bin/env bash
set -euo pipefail
cmd=$(jq -r '.tool_input.command // ""')
Why it's good: Fails fast on errors, unset variables, and pipe failures. </good_example>
<bad_example>
if [[ "$cmd" == *"rm -rf"* ]]; then
echo "Blocked dangerous command" >&2
exit 1 # Wrong!
fi
Why it's bad: Exit code 1 is non-blocking. Action will still proceed. </bad_example>
<good_example>
if [[ "$cmd" == *"rm -rf"* ]]; then
echo '{"decision": "block", "reason": "Dangerous command"}' >&2
exit 2 # Correct blocking exit code
fi
Why it's good: Exit code 2 blocks the action. JSON output provides context. </good_example>
<bad_example>
{
"hooks": {
"PreToolUse": [
{
"hooks": [{ "type": "prompt", "prompt": "Is this safe?" }]
}
]
}
}
Why it's bad: Prompt hooks only work for Stop and SubagentStop events. </bad_example>
<good_example>
{
"hooks": {
"PreToolUse": [
{
"hooks": [{ "type": "command", "command": "./validate.sh" }]
}
]
}
}
Why it's good: Command hooks work for all events. </good_example>
<bad_example>
if echo "$cmd" | grep -q 'rm'; then
exit 2
fi
Why it's bad: Too broad - blocks legitimate rm file.tmp.
</bad_example>
<good_example>
if echo "$cmd" | grep -qE 'rm\s+(-rf|-fr)\s+/'; then
exit 2
fi
Why it's good: Specific pattern targets actual dangerous commands. </good_example>
<bad_example>
log_file="/home/user/project/.claude/commands.log"
Why it's bad: Hardcoded path breaks on other machines. </bad_example>
<good_example>
log_file="$CLAUDE_PROJECT_DIR/.claude/commands.log"
Why it's good: Uses environment variable for portability. </good_example> </examples>
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.