spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / plugin-dev / skills / hook-development / SKILL.md
712 lines · 15.9 KBPreviewCodeRaw
1---
2name: Hook Development
3description: This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.
4version: 0.1.0
5---
6
7# Hook Development for Claude Code Plugins
8
9## Overview
10
11Hooks are event-driven automation scripts that execute in response to Claude Code events. Use hooks to validate operations, enforce policies, add context, and integrate external tools into workflows.
12
13**Key capabilities:**
14- Validate tool calls before execution (PreToolUse)
15- React to tool results (PostToolUse)
16- Enforce completion standards (Stop, SubagentStop)
17- Load project context (SessionStart)
18- Automate workflows across the development lifecycle
19
20## Hook Types
21
22### Prompt-Based Hooks (Recommended)
23
24Use LLM-driven decision making for context-aware validation:
25
26```json
27{
28 "type": "prompt",
29 "prompt": "Evaluate if this tool use is appropriate: $TOOL_INPUT",
30 "timeout": 30
31}
32```
33
34**Supported events:** Stop, SubagentStop, UserPromptSubmit, PreToolUse
35
36**Benefits:**
37- Context-aware decisions based on natural language reasoning
38- Flexible evaluation logic without bash scripting
39- Better edge case handling
40- Easier to maintain and extend
41
42### Command Hooks
43
44Execute bash commands for deterministic checks:
45
46```json
47{
48 "type": "command",
49 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
50 "timeout": 60
51}
52```
53
54**Use for:**
55- Fast deterministic validations
56- File system operations
57- External tool integrations
58- Performance-critical checks
59
60## Hook Configuration Formats
61
62### Plugin hooks.json Format
63
64**For plugin hooks** in `hooks/hooks.json`, use wrapper format:
65
66```json
67{
68 "description": "Brief explanation of hooks (optional)",
69 "hooks": {
70 "PreToolUse": [...],
71 "Stop": [...],
72 "SessionStart": [...]
73 }
74}
75```
76
77**Key points:**
78- `description` field is optional
79- `hooks` field is required wrapper containing actual hook events
80- This is the **plugin-specific format**
81
82**Example:**
83```json
84{
85 "description": "Validation hooks for code quality",
86 "hooks": {
87 "PreToolUse": [
88 {
89 "matcher": "Write",
90 "hooks": [
91 {
92 "type": "command",
93 "command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.sh"
94 }
95 ]
96 }
97 ]
98 }
99}
100```
101
102### Settings Format (Direct)
103
104**For user settings** in `.claude/settings.json`, use direct format:
105
106```json
107{
108 "PreToolUse": [...],
109 "Stop": [...],
110 "SessionStart": [...]
111}
112```
113
114**Key points:**
115- No wrapper - events directly at top level
116- No description field
117- This is the **settings format**
118
119**Important:** The examples below show the hook event structure that goes inside either format. For plugin hooks.json, wrap these in `{"hooks": {...}}`.
120
121## Hook Events
122
123### PreToolUse
124
125Execute before any tool runs. Use to approve, deny, or modify tool calls.
126
127**Example (prompt-based):**
128```json
129{
130 "PreToolUse": [
131 {
132 "matcher": "Write|Edit",
133 "hooks": [
134 {
135 "type": "prompt",
136 "prompt": "Validate file write safety. Check: system paths, credentials, path traversal, sensitive content. Return 'approve' or 'deny'."
137 }
138 ]
139 }
140 ]
141}
142```
143
144**Output for PreToolUse:**
145```json
146{
147 "hookSpecificOutput": {
148 "permissionDecision": "allow|deny|ask",
149 "updatedInput": {"field": "modified_value"}
150 },
151 "systemMessage": "Explanation for Claude"
152}
153```
154
155### PostToolUse
156
157Execute after tool completes. Use to react to results, provide feedback, or log.
158
159**Example:**
160```json
161{
162 "PostToolUse": [
163 {
164 "matcher": "Edit",
165 "hooks": [
166 {
167 "type": "prompt",
168 "prompt": "Analyze edit result for potential issues: syntax errors, security vulnerabilities, breaking changes. Provide feedback."
169 }
170 ]
171 }
172 ]
173}
174```
175
176**Output behavior:**
177- Exit 0: stdout shown in transcript
178- Exit 2: stderr fed back to Claude
179- systemMessage included in context
180
181### Stop
182
183Execute when main agent considers stopping. Use to validate completeness.
184
185**Example:**
186```json
187{
188 "Stop": [
189 {
190 "matcher": "*",
191 "hooks": [
192 {
193 "type": "prompt",
194 "prompt": "Verify task completion: tests run, build succeeded, questions answered. Return 'approve' to stop or 'block' with reason to continue."
195 }
196 ]
197 }
198 ]
199}
200```
201
202**Decision output:**
203```json
204{
205 "decision": "approve|block",
206 "reason": "Explanation",
207 "systemMessage": "Additional context"
208}
209```
210
211### SubagentStop
212
213Execute when subagent considers stopping. Use to ensure subagent completed its task.
214
215Similar to Stop hook, but for subagents.
216
217### UserPromptSubmit
218
219Execute when user submits a prompt. Use to add context, validate, or block prompts.
220
221**Example:**
222```json
223{
224 "UserPromptSubmit": [
225 {
226 "matcher": "*",
227 "hooks": [
228 {
229 "type": "prompt",
230 "prompt": "Check if prompt requires security guidance. If discussing auth, permissions, or API security, return relevant warnings."
231 }
232 ]
233 }
234 ]
235}
236```
237
238### SessionStart
239
240Execute when Claude Code session begins. Use to load context and set environment.
241
242**Example:**
243```json
244{
245 "SessionStart": [
246 {
247 "matcher": "*",
248 "hooks": [
249 {
250 "type": "command",
251 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
252 }
253 ]
254 }
255 ]
256}
257```
258
259**Special capability:** Persist environment variables using `$CLAUDE_ENV_FILE`:
260```bash
261echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
262```
263
264See `examples/load-context.sh` for complete example.
265
266### SessionEnd
267
268Execute when session ends. Use for cleanup, logging, and state preservation.
269
270### PreCompact
271
272Execute before context compaction. Use to add critical information to preserve.
273
274### Notification
275
276Execute when Claude sends notifications. Use to react to user notifications.
277
278## Hook Output Format
279
280### Standard Output (All Hooks)
281
282```json
283{
284 "continue": true,
285 "suppressOutput": false,
286 "systemMessage": "Message for Claude"
287}
288```
289
290- `continue`: If false, halt processing (default true)
291- `suppressOutput`: Hide output from transcript (default false)
292- `systemMessage`: Message shown to Claude
293
294### Exit Codes
295
296- `0` - Success (stdout shown in transcript)
297- `2` - Blocking error (stderr fed back to Claude)
298- Other - Non-blocking error
299
300## Hook Input Format
301
302All hooks receive JSON via stdin with common fields:
303
304```json
305{
306 "session_id": "abc123",
307 "transcript_path": "/path/to/transcript.txt",
308 "cwd": "/current/working/dir",
309 "permission_mode": "ask|allow",
310 "hook_event_name": "PreToolUse"
311}
312```
313
314**Event-specific fields:**
315
316- **PreToolUse/PostToolUse:** `tool_name`, `tool_input`, `tool_result`
317- **UserPromptSubmit:** `user_prompt`
318- **Stop/SubagentStop:** `reason`
319
320Access fields in prompts using `$TOOL_INPUT`, `$TOOL_RESULT`, `$USER_PROMPT`, etc.
321
322## Environment Variables
323
324Available in all command hooks:
325
326- `$CLAUDE_PROJECT_DIR` - Project root path
327- `$CLAUDE_PLUGIN_ROOT` - Plugin directory (use for portable paths)
328- `$CLAUDE_ENV_FILE` - SessionStart only: persist env vars here
329- `$CLAUDE_CODE_REMOTE` - Set if running in remote context
330
331**Always use ${CLAUDE_PLUGIN_ROOT} in hook commands for portability:**
332
333```json
334{
335 "type": "command",
336 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
337}
338```
339
340## Plugin Hook Configuration
341
342In plugins, define hooks in `hooks/hooks.json`:
343
344```json
345{
346 "PreToolUse": [
347 {
348 "matcher": "Write|Edit",
349 "hooks": [
350 {
351 "type": "prompt",
352 "prompt": "Validate file write safety"
353 }
354 ]
355 }
356 ],
357 "Stop": [
358 {
359 "matcher": "*",
360 "hooks": [
361 {
362 "type": "prompt",
363 "prompt": "Verify task completion"
364 }
365 ]
366 }
367 ],
368 "SessionStart": [
369 {
370 "matcher": "*",
371 "hooks": [
372 {
373 "type": "command",
374 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh",
375 "timeout": 10
376 }
377 ]
378 }
379 ]
380}
381```
382
383Plugin hooks merge with user's hooks and run in parallel.
384
385## Matchers
386
387### Tool Name Matching
388
389**Exact match:**
390```json
391"matcher": "Write"
392```
393
394**Multiple tools:**
395```json
396"matcher": "Read|Write|Edit"
397```
398
399**Wildcard (all tools):**
400```json
401"matcher": "*"
402```
403
404**Regex patterns:**
405```json
406"matcher": "mcp__.*__delete.*" // All MCP delete tools
407```
408
409**Note:** Matchers are case-sensitive.
410
411### Common Patterns
412
413```json
414// All MCP tools
415"matcher": "mcp__.*"
416
417// Specific plugin's MCP tools
418"matcher": "mcp__plugin_asana_.*"
419
420// All file operations
421"matcher": "Read|Write|Edit"
422
423// Bash commands only
424"matcher": "Bash"
425```
426
427## Security Best Practices
428
429### Input Validation
430
431Always validate inputs in command hooks:
432
433```bash
434#!/bin/bash
435set -euo pipefail
436
437input=$(cat)
438tool_name=$(echo "$input" | jq -r '.tool_name')
439
440# Validate tool name format
441if [[ ! "$tool_name" =~ ^[a-zA-Z0-9_]+$ ]]; then
442 echo '{"decision": "deny", "reason": "Invalid tool name"}' >&2
443 exit 2
444fi
445```
446
447### Path Safety
448
449Check for path traversal and sensitive files:
450
451```bash
452file_path=$(echo "$input" | jq -r '.tool_input.file_path')
453
454# Deny path traversal
455if [[ "$file_path" == *".."* ]]; then
456 echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2
457 exit 2
458fi
459
460# Deny sensitive files
461if [[ "$file_path" == *".env"* ]]; then
462 echo '{"decision": "deny", "reason": "Sensitive file"}' >&2
463 exit 2
464fi
465```
466
467See `examples/validate-write.sh` and `examples/validate-bash.sh` for complete examples.
468
469### Quote All Variables
470
471```bash
472# GOOD: Quoted
473echo "$file_path"
474cd "$CLAUDE_PROJECT_DIR"
475
476# BAD: Unquoted (injection risk)
477echo $file_path
478cd $CLAUDE_PROJECT_DIR
479```
480
481### Set Appropriate Timeouts
482
483```json
484{
485 "type": "command",
486 "command": "bash script.sh",
487 "timeout": 10
488}
489```
490
491**Defaults:** Command hooks (60s), Prompt hooks (30s)
492
493## Performance Considerations
494
495### Parallel Execution
496
497All matching hooks run **in parallel**:
498
499```json
500{
501 "PreToolUse": [
502 {
503 "matcher": "Write",
504 "hooks": [
505 {"type": "command", "command": "check1.sh"}, // Parallel
506 {"type": "command", "command": "check2.sh"}, // Parallel
507 {"type": "prompt", "prompt": "Validate..."} // Parallel
508 ]
509 }
510 ]
511}
512```
513
514**Design implications:**
515- Hooks don't see each other's output
516- Non-deterministic ordering
517- Design for independence
518
519### Optimization
520
5211. Use command hooks for quick deterministic checks
5222. Use prompt hooks for complex reasoning
5233. Cache validation results in temp files
5244. Minimize I/O in hot paths
525
526## Temporarily Active Hooks
527
528Create hooks that activate conditionally by checking for a flag file or configuration:
529
530**Pattern: Flag file activation**
531```bash
532#!/bin/bash
533# Only active when flag file exists
534FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-strict-validation"
535
536if [ ! -f "$FLAG_FILE" ]; then
537 # Flag not present, skip validation
538 exit 0
539fi
540
541# Flag present, run validation
542input=$(cat)
543# ... validation logic ...
544```
545
546**Pattern: Configuration-based activation**
547```bash
548#!/bin/bash
549# Check configuration for activation
550CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/plugin-config.json"
551
552if [ -f "$CONFIG_FILE" ]; then
553 enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE")
554 if [ "$enabled" != "true" ]; then
555 exit 0 # Not enabled, skip
556 fi
557fi
558
559# Enabled, run hook logic
560input=$(cat)
561# ... hook logic ...
562```
563
564**Use cases:**
565- Enable strict validation only when needed
566- Temporary debugging hooks
567- Project-specific hook behavior
568- Feature flags for hooks
569
570**Best practice:** Document activation mechanism in plugin README so users know how to enable/disable temporary hooks.
571
572## Hook Lifecycle and Limitations
573
574### Hooks Load at Session Start
575
576**Important:** Hooks are loaded when Claude Code session starts. Changes to hook configuration require restarting Claude Code.
577
578**Cannot hot-swap hooks:**
579- Editing `hooks/hooks.json` won't affect current session
580- Adding new hook scripts won't be recognized
581- Changing hook commands/prompts won't update
582- Must restart Claude Code: exit and run `claude` again
583
584**To test hook changes:**
5851. Edit hook configuration or scripts
5862. Exit Claude Code session
5873. Restart: `claude` or `cc`
5884. New hook configuration loads
5895. Test hooks with `claude --debug`
590
591### Hook Validation at Startup
592
593Hooks are validated when Claude Code starts:
594- Invalid JSON in hooks.json causes loading failure
595- Missing scripts cause warnings
596- Syntax errors reported in debug mode
597
598Use `/hooks` command to review loaded hooks in current session.
599
600## Debugging Hooks
601
602### Enable Debug Mode
603
604```bash
605claude --debug
606```
607
608Look for hook registration, execution logs, input/output JSON, and timing information.
609
610### Test Hook Scripts
611
612Test command hooks directly:
613
614```bash
615echo '{"tool_name": "Write", "tool_input": {"file_path": "/test"}}' | \
616 bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh
617
618echo "Exit code: $?"
619```
620
621### Validate JSON Output
622
623Ensure hooks output valid JSON:
624
625```bash
626output=$(./your-hook.sh < test-input.json)
627echo "$output" | jq .
628```
629
630## Quick Reference
631
632### Hook Events Summary
633
634| Event | When | Use For |
635|-------|------|---------|
636| PreToolUse | Before tool | Validation, modification |
637| PostToolUse | After tool | Feedback, logging |
638| UserPromptSubmit | User input | Context, validation |
639| Stop | Agent stopping | Completeness check |
640| SubagentStop | Subagent done | Task validation |
641| SessionStart | Session begins | Context loading |
642| SessionEnd | Session ends | Cleanup, logging |
643| PreCompact | Before compact | Preserve context |
644| Notification | User notified | Logging, reactions |
645
646### Best Practices
647
648**DO:**
649- ✅ Use prompt-based hooks for complex logic
650- ✅ Use ${CLAUDE_PLUGIN_ROOT} for portability
651- ✅ Validate all inputs in command hooks
652- ✅ Quote all bash variables
653- ✅ Set appropriate timeouts
654- ✅ Return structured JSON output
655- ✅ Test hooks thoroughly
656
657**DON'T:**
658- ❌ Use hardcoded paths
659- ❌ Trust user input without validation
660- ❌ Create long-running hooks
661- ❌ Rely on hook execution order
662- ❌ Modify global state unpredictably
663- ❌ Log sensitive information
664
665## Additional Resources
666
667### Reference Files
668
669For detailed patterns and advanced techniques, consult:
670
671- **`references/patterns.md`** - Common hook patterns (8+ proven patterns)
672- **`references/migration.md`** - Migrating from basic to advanced hooks
673- **`references/advanced.md`** - Advanced use cases and techniques
674
675### Example Hook Scripts
676
677Working examples in `examples/`:
678
679- **`validate-write.sh`** - File write validation example
680- **`validate-bash.sh`** - Bash command validation example
681- **`load-context.sh`** - SessionStart context loading example
682
683### Utility Scripts
684
685Development tools in `scripts/`:
686
687- **`validate-hook-schema.sh`** - Validate hooks.json structure and syntax
688- **`test-hook.sh`** - Test hooks with sample input before deployment
689- **`hook-linter.sh`** - Check hook scripts for common issues and best practices
690
691### External Resources
692
693- **Official Docs**: https://docs.claude.com/en/docs/claude-code/hooks
694- **Examples**: See security-guidance plugin in marketplace
695- **Testing**: Use `claude --debug` for detailed logs
696- **Validation**: Use `jq` to validate hook JSON output
697
698## Implementation Workflow
699
700To implement hooks in a plugin:
701
7021. Identify events to hook into (PreToolUse, Stop, SessionStart, etc.)
7032. Decide between prompt-based (flexible) or command (deterministic) hooks
7043. Write hook configuration in `hooks/hooks.json`
7054. For command hooks, create hook scripts
7065. Use ${CLAUDE_PLUGIN_ROOT} for all file references
7076. Validate configuration with `scripts/validate-hook-schema.sh hooks/hooks.json`
7087. Test hooks with `scripts/test-hook.sh` before deployment
7098. Test in Claude Code with `claude --debug`
7109. Document hooks in plugin README
711
712Focus on prompt-based hooks for most use cases. Reserve command hooks for performance-critical or deterministic checks.