hook-development

Hook Development for Claude Code Plugins

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hook-development" with this command: npx skills add josiahsiegel/claude-plugin-marketplace/josiahsiegel-claude-plugin-marketplace-hook-development

Hook Development for Claude Code Plugins

Overview

Hooks are event-driven automation that execute in response to Claude Code events. Use hooks to validate operations, enforce policies, load context, and integrate external tools.

Two hook types:

  • Prompt-based (recommended): LLM-driven, context-aware decisions

  • Command-based: Shell commands for fast, deterministic checks

Hook Events Reference

Event When Common Use

PreToolUse Before tool executes Validate, approve/deny, modify input

PostToolUse After tool completes Test, lint, log, provide feedback

Stop Main agent stopping Verify task completeness

SubagentStop Subagent stopping Validate subagent work

UserPromptSubmit User sends prompt Add context, validate, preprocess

SessionStart Session begins Load context, set environment

SessionEnd Session ends Cleanup, logging

PreCompact Before context compaction Preserve critical information

Notification Notification shown Custom alert reactions

Configuration Formats

Plugin hooks.json (in hooks/hooks.json )

Uses wrapper format with hooks field:

{ "description": "What these hooks do (optional)", "hooks": { "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh", "timeout": 10 } ] } ] } }

User settings format (in .claude/settings.json )

Direct format, no wrapper:

{ "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "script.sh" }] } ] }

Critical difference: Plugin hooks.json wraps events inside {"hooks": {...}} . Settings format puts events at top level.

Prompt-Based Hooks (Recommended)

Use LLM reasoning for context-aware decisions:

{ "type": "prompt", "prompt": "Evaluate if this tool use is appropriate. Check for: system paths, credentials, path traversal. Return 'approve' or 'deny'.", "timeout": 30 }

Supported events: PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit

Benefits: Context-aware, flexible, better edge case handling, easier to maintain.

Command Hooks

Execute shell commands for deterministic checks:

{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh", "timeout": 60 }

Always use ${CLAUDE_PLUGIN_ROOT} for portable paths.

Exit Codes

Code Meaning

0 Success (stdout shown in transcript)

2 Blocking error (stderr fed back to Claude)

Other Non-blocking error

Matchers

Control which tools trigger hooks:

"matcher": "Write" // Exact match "matcher": "Write|Edit|Bash" // Multiple tools "matcher": "mcp__.__delete." // Regex (all MCP delete tools) "matcher": "*" // All tools (use sparingly)

Matchers are case-sensitive.

Hook Input/Output

Input (all hooks receive via stdin)

{ "session_id": "abc123", "transcript_path": "/path/to/transcript.txt", "cwd": "/current/working/dir", "hook_event_name": "PreToolUse", "tool_name": "Write", "tool_input": { "file_path": "/path/to/file" } }

Event-specific fields: tool_name , tool_input , tool_result , user_prompt , reason

Access in prompts: $TOOL_INPUT , $TOOL_RESULT , $USER_PROMPT

Output

Standard (all hooks):

{ "continue": true, "suppressOutput": false, "systemMessage": "Message for Claude" }

PreToolUse decisions:

{ "hookSpecificOutput": { "permissionDecision": "allow|deny|ask", "updatedInput": { "field": "modified_value" } } }

Stop/SubagentStop decisions:

{ "decision": "approve|block", "reason": "Explanation" }

Environment Variables

Variable Available Purpose

$CLAUDE_PLUGIN_ROOT

All hooks Plugin directory (portable paths)

$CLAUDE_PROJECT_DIR

All hooks Project root path

$CLAUDE_ENV_FILE

SessionStart only Persist env vars for session

SessionStart can persist variables:

echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"

Common Patterns

Validate file writes (PreToolUse)

{ "PreToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "prompt", "prompt": "Check if this file write is safe. Deny writes to: .env, credentials, system paths, or files with path traversal (..). Return 'approve' or 'deny' with reason." }] }] }

Auto-test after changes (PostToolUse)

{ "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "npm test -- --bail", "timeout": 60 }] }] }

Verify task completion (Stop)

{ "Stop": [{ "matcher": "*", "hooks": [{ "type": "prompt", "prompt": "Verify: tests run, build succeeded, all questions answered. Return 'approve' to stop or 'block' with reason to continue." }] }] }

Load project context (SessionStart)

{ "SessionStart": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh", "timeout": 10 }] }] }

Security Best Practices

In command hook scripts:

#!/bin/bash set -euo pipefail

input=$(cat) file_path=$(echo "$input" | jq -r '.tool_input.file_path')

Always validate inputs

if [[ ! "$file_path" =~ ^[a-zA-Z0-9_./-]+$ ]]; then echo '{"decision": "deny", "reason": "Invalid path"}' >&2 exit 2 fi

Block path traversal

if [[ "$file_path" == ".." ]]; then echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2 exit 2 fi

Block sensitive files

if [[ "$file_path" == ".env" ]]; then echo '{"decision": "deny", "reason": "Sensitive file"}' >&2 exit 2 fi

Always quote variables

echo "$file_path"

Lifecycle and Limitations

Hooks load at session start. Changes to hook configuration require restarting Claude Code.

  • Editing hooks/hooks.json won't affect the current session

  • Adding new hook scripts won't be recognized until restart

  • All matching hooks run in parallel (not sequentially)

  • Hooks don't see each other's output - design for independence

To test changes: Exit Claude Code, restart with claude or claude --debug .

Debugging

Enable debug mode to see hook execution

claude --debug

Test hook scripts directly

echo '{"tool_name": "Write", "tool_input": {"file_path": "/test"}}' |
bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh

Validate hook JSON output

output=$(./hook-script.sh < test-input.json) echo "$output" | jq .

View loaded hooks in session

Use /hooks command

Validation Checklist

  • hooks.json uses correct format (plugin wrapper or settings direct)

  • All script paths use ${CLAUDE_PLUGIN_ROOT} (no hardcoded paths)

  • Scripts are executable and handle errors (set -euo pipefail )

  • Scripts validate all inputs and quote all variables

  • Matchers are specific (avoid * unless necessary)

  • Timeouts are set appropriately (default: command 60s, prompt 30s)

  • Hook output is valid JSON

  • Tested with claude --debug

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

defender-for-devops

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

github-actions-2025

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

github-ai-features-2025

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

skill-development

No summary provided by upstream source.

Repository SourceNeeds Review