Agentsys enhance-hooks

Use when reviewing hooks for safety, timeouts, and correct frontmatter.

install
source · Clone the upstream repo
git clone https://github.com/agent-sh/agentsys
Claude Code · Install into ~/.claude/skills/
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"
manifest: .kiro/skills/enhance-hooks/SKILL.md
source content

enhance-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

  1. Discover - Find hook files (.md, .sh, .json)
  2. Classify - Identify hook type and event
  3. Parse - Extract frontmatter and script content
  4. Check - Run all pattern checks against knowledge below
  5. Filter - Apply certainty filtering
  6. Report - Generate markdown output
  7. 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:

OrderEventDescriptionMatcher Required
1
SessionStart
Session begins or resumesNo
2
UserPromptSubmit
User submits a promptNo
3
PreToolUse
Before tool execution (can modify/block)Yes
4
PermissionRequest
When permission dialog appearsYes
5
PostToolUse
After tool succeedsYes
6
SubagentStart
When spawning a subagentNo
7
SubagentStop
When subagent finishesNo
8
Stop
Claude finishes respondingNo
9
PreCompact
Before context compactionNo
10
SessionEnd
Session terminatesNo
11
Notification
Claude Code sends notificationsNo

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
    Stop
    and
    SubagentStop
    events

Configuration Locations

FileLocationScopeCommitted
User settings
~/.claude/settings.json
All projectsNo
Project settings
.claude/settings.json
Current projectYes
Local settings
.claude/settings.local.json
Current projectNo

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

PatternDescription
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

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 CodeBehavior
0Success - stdout shown to user or added as context
2Blocking error - stderr shown, action blocked
OtherNon-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

VariableDescriptionAvailable In
CLAUDE_PROJECT_DIR
Absolute path to project rootAll hooks
CLAUDE_CODE_REMOTE
"true" if remote sessionAll hooks
CLAUDE_ENV_FILE
Path to persist env varsSessionStart only
CLAUDE_FILE_PATHS
Space-separated file pathsPostToolUse (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
  • name
    field in frontmatter
  • description
    field in frontmatter

Recommended:

  • timeout
    for command hooks (default: 30s)
  • Hook type specification

Flag:

  • Missing frontmatter delimiters
  • Missing name or description

2. Script Safety (HIGH Certainty)

Required Safety Patterns:

  • set -euo pipefail
    at script start
  • Error handling for jq/JSON parsing
  • Proper quoting of variables

Dangerous Patterns to Flag:

PatternRiskCertainty
rm -rf
Destructive without confirmationHIGH
git reset --hard
Data loss riskHIGH
curl | sh
Remote code executionHIGH
eval "$input"
Arbitrary code executionHIGH
rm -r
Recursive delete (may be intentional)MEDIUM
git push --force
Force push (may be intentional)MEDIUM

3. Exit Code Handling (HIGH Certainty)

Check: Scripts use correct exit codes

Flag:

  • Missing
    exit 0
    for success path
  • 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)

EventAppropriate 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:

  • 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:

  • permissionDecision
    : allow, deny, or ask
  • permissionDecisionReason
    : Explanation for decision
  • updatedInput
    : Modified tool input (optional)
  • additionalContext
    : Context for Claude (optional)

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

CategoryPatternsAuto-Fixable
Frontmatter32
Safety62
Exit Code32
Hook Type20
Lifecycle50
Timeout30
Output30
Matcher30
Anti-Pattern50
Total336

<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