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
764 lines ยท 17.3 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", "teleport hook", 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**Matchers for SessionStart:**
243- `*` - All session starts
244- `teleport` - Only when session started via teleport (web โ†’ CLI)
245- `fresh` - Only for fresh sessions (not teleported)
246
247**Example (general context loading):**
248```json
249{
250 "SessionStart": [
251 {
252 "matcher": "*",
253 "hooks": [
254 {
255 "type": "command",
256 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
257 }
258 ]
259 }
260 ]
261}
262```
263
264**Example (teleport-specific setup):**
265```json
266{
267 "SessionStart": [
268 {
269 "matcher": "teleport",
270 "hooks": [
271 {
272 "type": "command",
273 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/post-teleport.sh"
274 }
275 ]
276 }
277 ]
278}
279```
280
281**Example script (post-teleport.sh):**
282```bash
283#!/bin/bash
284cd "$CLAUDE_PROJECT_DIR" || exit 0
285
286# Pull latest changes from the teleported branch
287if [ -d ".git" ]; then
288 echo "๐Ÿ”„ Pulling latest changes..."
289 git pull origin "$(git branch --show-current)" 2>/dev/null || true
290fi
291
292# Install dependencies if needed
293if [ -f "package.json" ]; then
294 echo "๐Ÿ“ฆ Installing dependencies..."
295 npm install --silent
296fi
297
298# Start dev server (example for common workflow)
299if [ -f "package.json" ] && grep -q '"dev"' package.json; then
300 echo "๐Ÿš€ Starting dev server..."
301 npm run dev &
302fi
303```
304
305**Special capability:** Persist environment variables using `$CLAUDE_ENV_FILE`:
306```bash
307echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
308```
309
310**Teleport-specific input fields:**
311- `is_teleport`: Boolean indicating if this session started via teleport
312- `source`: Where the session came from ("web" or "cli") - only present for teleports
313- `branch`: The git branch that was teleported - only present for teleports
314
315See `examples/load-context.sh` and `examples/post-teleport.sh` for complete examples.
316
317### SessionEnd
318
319Execute when session ends. Use for cleanup, logging, and state preservation.
320
321### PreCompact
322
323Execute before context compaction. Use to add critical information to preserve.
324
325### Notification
326
327Execute when Claude sends notifications. Use to react to user notifications.
328
329## Hook Output Format
330
331### Standard Output (All Hooks)
332
333```json
334{
335 "continue": true,
336 "suppressOutput": false,
337 "systemMessage": "Message for Claude"
338}
339```
340
341- `continue`: If false, halt processing (default true)
342- `suppressOutput`: Hide output from transcript (default false)
343- `systemMessage`: Message shown to Claude
344
345### Exit Codes
346
347- `0` - Success (stdout shown in transcript)
348- `2` - Blocking error (stderr fed back to Claude)
349- Other - Non-blocking error
350
351## Hook Input Format
352
353All hooks receive JSON via stdin with common fields:
354
355```json
356{
357 "session_id": "abc123",
358 "transcript_path": "/path/to/transcript.txt",
359 "cwd": "/current/working/dir",
360 "permission_mode": "ask|allow",
361 "hook_event_name": "PreToolUse"
362}
363```
364
365**Event-specific fields:**
366
367- **PreToolUse/PostToolUse:** `tool_name`, `tool_input`, `tool_result`
368- **UserPromptSubmit:** `user_prompt`
369- **Stop/SubagentStop:** `reason`
370
371Access fields in prompts using `$TOOL_INPUT`, `$TOOL_RESULT`, `$USER_PROMPT`, etc.
372
373## Environment Variables
374
375Available in all command hooks:
376
377- `$CLAUDE_PROJECT_DIR` - Project root path
378- `$CLAUDE_PLUGIN_ROOT` - Plugin directory (use for portable paths)
379- `$CLAUDE_ENV_FILE` - SessionStart only: persist env vars here
380- `$CLAUDE_CODE_REMOTE` - Set if running in remote context
381
382**Always use ${CLAUDE_PLUGIN_ROOT} in hook commands for portability:**
383
384```json
385{
386 "type": "command",
387 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
388}
389```
390
391## Plugin Hook Configuration
392
393In plugins, define hooks in `hooks/hooks.json`:
394
395```json
396{
397 "PreToolUse": [
398 {
399 "matcher": "Write|Edit",
400 "hooks": [
401 {
402 "type": "prompt",
403 "prompt": "Validate file write safety"
404 }
405 ]
406 }
407 ],
408 "Stop": [
409 {
410 "matcher": "*",
411 "hooks": [
412 {
413 "type": "prompt",
414 "prompt": "Verify task completion"
415 }
416 ]
417 }
418 ],
419 "SessionStart": [
420 {
421 "matcher": "*",
422 "hooks": [
423 {
424 "type": "command",
425 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh",
426 "timeout": 10
427 }
428 ]
429 }
430 ]
431}
432```
433
434Plugin hooks merge with user's hooks and run in parallel.
435
436## Matchers
437
438### Tool Name Matching
439
440**Exact match:**
441```json
442"matcher": "Write"
443```
444
445**Multiple tools:**
446```json
447"matcher": "Read|Write|Edit"
448```
449
450**Wildcard (all tools):**
451```json
452"matcher": "*"
453```
454
455**Regex patterns:**
456```json
457"matcher": "mcp__.*__delete.*" // All MCP delete tools
458```
459
460**Note:** Matchers are case-sensitive.
461
462### Common Patterns
463
464```json
465// All MCP tools
466"matcher": "mcp__.*"
467
468// Specific plugin's MCP tools
469"matcher": "mcp__plugin_asana_.*"
470
471// All file operations
472"matcher": "Read|Write|Edit"
473
474// Bash commands only
475"matcher": "Bash"
476```
477
478## Security Best Practices
479
480### Input Validation
481
482Always validate inputs in command hooks:
483
484```bash
485#!/bin/bash
486set -euo pipefail
487
488input=$(cat)
489tool_name=$(echo "$input" | jq -r '.tool_name')
490
491# Validate tool name format
492if [[ ! "$tool_name" =~ ^[a-zA-Z0-9_]+$ ]]; then
493 echo '{"decision": "deny", "reason": "Invalid tool name"}' >&2
494 exit 2
495fi
496```
497
498### Path Safety
499
500Check for path traversal and sensitive files:
501
502```bash
503file_path=$(echo "$input" | jq -r '.tool_input.file_path')
504
505# Deny path traversal
506if [[ "$file_path" == *".."* ]]; then
507 echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2
508 exit 2
509fi
510
511# Deny sensitive files
512if [[ "$file_path" == *".env"* ]]; then
513 echo '{"decision": "deny", "reason": "Sensitive file"}' >&2
514 exit 2
515fi
516```
517
518See `examples/validate-write.sh` and `examples/validate-bash.sh` for complete examples.
519
520### Quote All Variables
521
522```bash
523# GOOD: Quoted
524echo "$file_path"
525cd "$CLAUDE_PROJECT_DIR"
526
527# BAD: Unquoted (injection risk)
528echo $file_path
529cd $CLAUDE_PROJECT_DIR
530```
531
532### Set Appropriate Timeouts
533
534```json
535{
536 "type": "command",
537 "command": "bash script.sh",
538 "timeout": 10
539}
540```
541
542**Defaults:** Command hooks (60s), Prompt hooks (30s)
543
544## Performance Considerations
545
546### Parallel Execution
547
548All matching hooks run **in parallel**:
549
550```json
551{
552 "PreToolUse": [
553 {
554 "matcher": "Write",
555 "hooks": [
556 {"type": "command", "command": "check1.sh"}, // Parallel
557 {"type": "command", "command": "check2.sh"}, // Parallel
558 {"type": "prompt", "prompt": "Validate..."} // Parallel
559 ]
560 }
561 ]
562}
563```
564
565**Design implications:**
566- Hooks don't see each other's output
567- Non-deterministic ordering
568- Design for independence
569
570### Optimization
571
5721. Use command hooks for quick deterministic checks
5732. Use prompt hooks for complex reasoning
5743. Cache validation results in temp files
5754. Minimize I/O in hot paths
576
577## Temporarily Active Hooks
578
579Create hooks that activate conditionally by checking for a flag file or configuration:
580
581**Pattern: Flag file activation**
582```bash
583#!/bin/bash
584# Only active when flag file exists
585FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-strict-validation"
586
587if [ ! -f "$FLAG_FILE" ]; then
588 # Flag not present, skip validation
589 exit 0
590fi
591
592# Flag present, run validation
593input=$(cat)
594# ... validation logic ...
595```
596
597**Pattern: Configuration-based activation**
598```bash
599#!/bin/bash
600# Check configuration for activation
601CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/plugin-config.json"
602
603if [ -f "$CONFIG_FILE" ]; then
604 enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE")
605 if [ "$enabled" != "true" ]; then
606 exit 0 # Not enabled, skip
607 fi
608fi
609
610# Enabled, run hook logic
611input=$(cat)
612# ... hook logic ...
613```
614
615**Use cases:**
616- Enable strict validation only when needed
617- Temporary debugging hooks
618- Project-specific hook behavior
619- Feature flags for hooks
620
621**Best practice:** Document activation mechanism in plugin README so users know how to enable/disable temporary hooks.
622
623## Hook Lifecycle and Limitations
624
625### Hooks Load at Session Start
626
627**Important:** Hooks are loaded when Claude Code session starts. Changes to hook configuration require restarting Claude Code.
628
629**Cannot hot-swap hooks:**
630- Editing `hooks/hooks.json` won't affect current session
631- Adding new hook scripts won't be recognized
632- Changing hook commands/prompts won't update
633- Must restart Claude Code: exit and run `claude` again
634
635**To test hook changes:**
6361. Edit hook configuration or scripts
6372. Exit Claude Code session
6383. Restart: `claude` or `cc`
6394. New hook configuration loads
6405. Test hooks with `claude --debug`
641
642### Hook Validation at Startup
643
644Hooks are validated when Claude Code starts:
645- Invalid JSON in hooks.json causes loading failure
646- Missing scripts cause warnings
647- Syntax errors reported in debug mode
648
649Use `/hooks` command to review loaded hooks in current session.
650
651## Debugging Hooks
652
653### Enable Debug Mode
654
655```bash
656claude --debug
657```
658
659Look for hook registration, execution logs, input/output JSON, and timing information.
660
661### Test Hook Scripts
662
663Test command hooks directly:
664
665```bash
666echo '{"tool_name": "Write", "tool_input": {"file_path": "/test"}}' | \
667 bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh
668
669echo "Exit code: $?"
670```
671
672### Validate JSON Output
673
674Ensure hooks output valid JSON:
675
676```bash
677output=$(./your-hook.sh < test-input.json)
678echo "$output" | jq .
679```
680
681## Quick Reference
682
683### Hook Events Summary
684
685| Event | When | Use For |
686|-------|------|---------|
687| PreToolUse | Before tool | Validation, modification |
688| PostToolUse | After tool | Feedback, logging |
689| UserPromptSubmit | User input | Context, validation |
690| Stop | Agent stopping | Completeness check |
691| SubagentStop | Subagent done | Task validation |
692| SessionStart | Session begins | Context loading (use `teleport` matcher for teleport-specific setup) |
693| SessionEnd | Session ends | Cleanup, logging |
694| PreCompact | Before compact | Preserve context |
695| Notification | User notified | Logging, reactions |
696
697### Best Practices
698
699**DO:**
700- โœ… Use prompt-based hooks for complex logic
701- โœ… Use ${CLAUDE_PLUGIN_ROOT} for portability
702- โœ… Validate all inputs in command hooks
703- โœ… Quote all bash variables
704- โœ… Set appropriate timeouts
705- โœ… Return structured JSON output
706- โœ… Test hooks thoroughly
707
708**DON'T:**
709- โŒ Use hardcoded paths
710- โŒ Trust user input without validation
711- โŒ Create long-running hooks
712- โŒ Rely on hook execution order
713- โŒ Modify global state unpredictably
714- โŒ Log sensitive information
715
716## Additional Resources
717
718### Reference Files
719
720For detailed patterns and advanced techniques, consult:
721
722- **`references/patterns.md`** - Common hook patterns (8+ proven patterns)
723- **`references/migration.md`** - Migrating from basic to advanced hooks
724- **`references/advanced.md`** - Advanced use cases and techniques
725
726### Example Hook Scripts
727
728Working examples in `examples/`:
729
730- **`validate-write.sh`** - File write validation example
731- **`validate-bash.sh`** - Bash command validation example
732- **`load-context.sh`** - SessionStart context loading example
733- **`post-teleport.sh`** - SessionStart teleport matcher setup example
734
735### Utility Scripts
736
737Development tools in `scripts/`:
738
739- **`validate-hook-schema.sh`** - Validate hooks.json structure and syntax
740- **`test-hook.sh`** - Test hooks with sample input before deployment
741- **`hook-linter.sh`** - Check hook scripts for common issues and best practices
742
743### External Resources
744
745- **Official Docs**: https://docs.claude.com/en/docs/claude-code/hooks
746- **Examples**: See security-guidance plugin in marketplace
747- **Testing**: Use `claude --debug` for detailed logs
748- **Validation**: Use `jq` to validate hook JSON output
749
750## Implementation Workflow
751
752To implement hooks in a plugin:
753
7541. Identify events to hook into (PreToolUse, Stop, SessionStart, etc.)
7552. Decide between prompt-based (flexible) or command (deterministic) hooks
7563. Write hook configuration in `hooks/hooks.json`
7574. For command hooks, create hook scripts
7585. Use ${CLAUDE_PLUGIN_ROOT} for all file references
7596. Validate configuration with `scripts/validate-hook-schema.sh hooks/hooks.json`
7607. Test hooks with `scripts/test-hook.sh` before deployment
7618. Test in Claude Code with `claude --debug`
7629. Document hooks in plugin README
763
764Focus on prompt-based hooks for most use cases. Reserve command hooks for performance-critical or deterministic checks.