Agentsys enhance-hooks
Use when reviewing hooks for safety, timeouts, and correct frontmatter.
git clone https://github.com/agent-sh/agentsys
T=$(mktemp -d) && git clone --depth=1 https://github.com/agent-sh/agentsys "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.kiro/skills/enhance-hooks" ~/.claude/skills/agent-sh-agentsys-enhance-hooks && rm -rf "$T"
.kiro/skills/enhance-hooks/SKILL.mdenhance-hooks
Analyze hook definitions and scripts for safety, correctness, and best practices.
Parse Arguments
const args = '$ARGUMENTS'.split(' ').filter(Boolean); const targetPath = args.find(a => !a.startsWith('--')) || '.'; const fix = args.includes('--fix');
Workflow
- Discover - Find hook files (.md, .sh, .json)
- Classify - Identify hook type and event
- Parse - Extract frontmatter and script content
- Check - Run all pattern checks against knowledge below
- Filter - Apply certainty filtering
- Report - Generate markdown output
- Fix - Apply auto-fixes if --fix flag present
Hook Knowledge Reference
What Are Hooks
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.
Hook Lifecycle (Complete Reference)
Hooks fire in this sequence:
| Order | Event | Description | Matcher Required |
|---|---|---|---|
| 1 | | Session begins or resumes | No |
| 2 | | User submits a prompt | No |
| 3 | | Before tool execution (can modify/block) | Yes |
| 4 | | When permission dialog appears | Yes |
| 5 | | After tool succeeds | Yes |
| 6 | | When spawning a subagent | No |
| 7 | | When subagent finishes | No |
| 8 | | Claude finishes responding | No |
| 9 | | Before context compaction | No |
| 10 | | Session terminates | No |
| 11 | | Claude Code sends notifications | No |
Hook Types
Command Hooks (
type: "command"):
- Execute bash commands with full stdin/stdout control
- Available for all events
Prompt Hooks (
type: "prompt"):
- Use LLM evaluation for intelligent, context-aware decisions
- Only supported for
andStop
eventsSubagentStop
Configuration Locations
| File | Location | Scope | Committed |
|---|---|---|---|
| User settings | | All projects | No |
| Project settings | | Current project | Yes |
| Local settings | | Current project | No |
Configuration Structure
{ "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 } ] } ] } }
Matcher Syntax
| Pattern | Description |
|---|---|
| Match exact tool name |
| Match multiple tools (regex OR) |
| Regex pattern matching |
or | Match all tools |
| (omitted) | Required for Stop, SubagentStop, UserPromptSubmit |
Input Schema (JSON via stdin)
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 Codes
| 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 |
Output Schemas
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" }
Environment Variables
| Variable | Description | Available In |
|---|---|---|
| Absolute path to project root | All hooks |
| "true" if remote session | All hooks |
| Path to persist env vars | SessionStart only |
| Space-separated file paths | PostToolUse (Write/Edit) |
Practical Hook Examples
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?" } ] } ] } }
Detection Patterns
1. Frontmatter Validation (HIGH Certainty)
Required:
- YAML frontmatter with
delimiters---
field in frontmattername
field in frontmatterdescription
Recommended:
for command hooks (default: 30s)timeout- Hook type specification
Flag:
- Missing frontmatter delimiters
- Missing name or description
2. Script Safety (HIGH Certainty)
Required Safety Patterns:
at script startset -euo pipefail- Error handling for jq/JSON parsing
- Proper quoting of variables
Dangerous Patterns to Flag:
| Pattern | Risk | Certainty |
|---|---|---|
| Destructive without confirmation | HIGH |
| Data loss risk | HIGH |
| Remote code execution | HIGH |
| Arbitrary code execution | HIGH |
| Recursive delete (may be intentional) | MEDIUM |
| Force push (may be intentional) | MEDIUM |
3. Exit Code Handling (HIGH Certainty)
Check: Scripts use correct exit codes
Flag:
- Missing
for success pathexit 0 - Using exit code 1 for blocking (should be 2)
- No exit code at end of script
4. Hook Type Appropriateness (HIGH Certainty)
Check: Hook type matches event
Flag:
- Prompt hooks used for events other than Stop/SubagentStop
- Missing type specification
5. Lifecycle Event Appropriateness (MEDIUM Certainty)
| Event | Appropriate Use Cases |
|---|---|
| Security validation, command blocking, input modification |
| Formatting, logging, notifications |
| Completion checks, cleanup, summary |
| Workflow orchestration, result validation |
| Environment setup, initialization |
Flag:
- PostToolUse hooks trying to block actions (too late)
- PreToolUse hooks doing heavy processing (should be fast)
- Prompt hooks on unsupported events
6. Timeout Configuration (MEDIUM Certainty)
Guidelines:
- Default: 30 seconds for command hooks
- Network operations: Always set explicit timeout
- External service calls: Set timeout based on expected latency
Flag:
- No timeout for network operations
- Timeout missing for external service calls
- Unreasonably long timeouts (>60s without justification)
7. Output Format (MEDIUM Certainty)
PreToolUse Output Fields:
: allow, deny, or askpermissionDecision
: Explanation for decisionpermissionDecisionReason
: Modified tool input (optional)updatedInput
: Context for Claude (optional)additionalContext
Flag:
- Invalid permissionDecision values
- Missing reason for deny decisions
- Malformed JSON output
8. Matcher Patterns (MEDIUM Certainty)
Check: Matcher syntax is valid
Flag:
- Invalid regex patterns
- Too broad matchers (
without justification)* - Matcher on events that don't support it (Stop, SubagentStop)
9. Anti-Patterns (LOW Certainty)
- Complex logic in hooks (should be simple and fast)
- Missing documentation/comments
- Hardcoded paths (should use
)$CLAUDE_PROJECT_DIR - Network calls without error handling
- Secrets/credentials in hook scripts
Auto-Fix Implementations
1. Missing safety header
#!/usr/bin/env bash set -euo pipefail
2. Missing exit code
Add
exit 0 at end of script
3. Missing frontmatter fields
--- name: hook-name description: Hook description timeout: 30 ---
4. Wrong blocking exit code
Replace
exit 1 with exit 2 for blocking errors
Output Format
## 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 |
Pattern Statistics
| 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 |
<examples> ### Example: Missing Safety Header
<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>
Example: Wrong Exit Code for Blocking
<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>
Example: Prompt Hook on Wrong Event
<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>
Example: Dangerous Command Pattern
<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>
Example: Hardcoded Path
<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>
Constraints
- Only apply auto-fixes for HIGH certainty issues
- Be cautious about security patterns - false negatives worse than false positives
- Never remove content, only suggest improvements
- Validate against embedded knowledge reference above