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 / references / advanced.md
479 lines · 9.9 KBPreviewCodeRaw
1# Advanced Hook Use Cases
2
3This reference covers advanced hook patterns and techniques for sophisticated automation workflows.
4
5## Multi-Stage Validation
6
7Combine command and prompt hooks for layered validation:
8
9```json
10{
11 "PreToolUse": [
12 {
13 "matcher": "Bash",
14 "hooks": [
15 {
16 "type": "command",
17 "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/quick-check.sh",
18 "timeout": 5
19 },
20 {
21 "type": "prompt",
22 "prompt": "Deep analysis of bash command: $TOOL_INPUT",
23 "timeout": 15
24 }
25 ]
26 }
27 ]
28}
29```
30
31**Use case:** Fast deterministic checks followed by intelligent analysis
32
33**Example quick-check.sh:**
34```bash
35#!/bin/bash
36input=$(cat)
37command=$(echo "$input" | jq -r '.tool_input.command')
38
39# Immediate approval for safe commands
40if [[ "$command" =~ ^(ls|pwd|echo|date|whoami)$ ]]; then
41 exit 0
42fi
43
44# Let prompt hook handle complex cases
45exit 0
46```
47
48The command hook quickly approves obviously safe commands, while the prompt hook analyzes everything else.
49
50## Conditional Hook Execution
51
52Execute hooks based on environment or context:
53
54```bash
55#!/bin/bash
56# Only run in CI environment
57if [ -z "$CI" ]; then
58 echo '{"continue": true}' # Skip in non-CI
59 exit 0
60fi
61
62# Run validation logic in CI
63input=$(cat)
64# ... validation code ...
65```
66
67**Use cases:**
68- Different behavior in CI vs local development
69- Project-specific validation
70- User-specific rules
71
72**Example: Skip certain checks for trusted users:**
73```bash
74#!/bin/bash
75# Skip detailed checks for admin users
76if [ "$USER" = "admin" ]; then
77 exit 0
78fi
79
80# Full validation for other users
81input=$(cat)
82# ... validation code ...
83```
84
85## Hook Chaining via State
86
87Share state between hooks using temporary files:
88
89```bash
90# Hook 1: Analyze and save state
91#!/bin/bash
92input=$(cat)
93command=$(echo "$input" | jq -r '.tool_input.command')
94
95# Analyze command
96risk_level=$(calculate_risk "$command")
97echo "$risk_level" > /tmp/hook-state-$$
98
99exit 0
100```
101
102```bash
103# Hook 2: Use saved state
104#!/bin/bash
105risk_level=$(cat /tmp/hook-state-$$ 2>/dev/null || echo "unknown")
106
107if [ "$risk_level" = "high" ]; then
108 echo "High risk operation detected" >&2
109 exit 2
110fi
111```
112
113**Important:** This only works for sequential hook events (e.g., PreToolUse then PostToolUse), not parallel hooks.
114
115## Dynamic Hook Configuration
116
117Modify hook behavior based on project configuration:
118
119```bash
120#!/bin/bash
121cd "$CLAUDE_PROJECT_DIR" || exit 1
122
123# Read project-specific config
124if [ -f ".claude-hooks-config.json" ]; then
125 strict_mode=$(jq -r '.strict_mode' .claude-hooks-config.json)
126
127 if [ "$strict_mode" = "true" ]; then
128 # Apply strict validation
129 # ...
130 else
131 # Apply lenient validation
132 # ...
133 fi
134fi
135```
136
137**Example .claude-hooks-config.json:**
138```json
139{
140 "strict_mode": true,
141 "allowed_commands": ["ls", "pwd", "grep"],
142 "forbidden_paths": ["/etc", "/sys"]
143}
144```
145
146## Context-Aware Prompt Hooks
147
148Use transcript and session context for intelligent decisions:
149
150```json
151{
152 "Stop": [
153 {
154 "matcher": "*",
155 "hooks": [
156 {
157 "type": "prompt",
158 "prompt": "Review the full transcript at $TRANSCRIPT_PATH. Check: 1) Were tests run after code changes? 2) Did the build succeed? 3) Were all user questions answered? 4) Is there any unfinished work? Return 'approve' only if everything is complete."
159 }
160 ]
161 }
162 ]
163}
164```
165
166The LLM can read the transcript file and make context-aware decisions.
167
168## Performance Optimization
169
170### Caching Validation Results
171
172```bash
173#!/bin/bash
174input=$(cat)
175file_path=$(echo "$input" | jq -r '.tool_input.file_path')
176cache_key=$(echo -n "$file_path" | md5sum | cut -d' ' -f1)
177cache_file="/tmp/hook-cache-$cache_key"
178
179# Check cache
180if [ -f "$cache_file" ]; then
181 cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file")))
182 if [ "$cache_age" -lt 300 ]; then # 5 minute cache
183 cat "$cache_file"
184 exit 0
185 fi
186fi
187
188# Perform validation
189result='{"decision": "approve"}'
190
191# Cache result
192echo "$result" > "$cache_file"
193echo "$result"
194```
195
196### Parallel Execution Optimization
197
198Since hooks run in parallel, design them to be independent:
199
200```json
201{
202 "PreToolUse": [
203 {
204 "matcher": "Write",
205 "hooks": [
206 {
207 "type": "command",
208 "command": "bash check-size.sh", // Independent
209 "timeout": 2
210 },
211 {
212 "type": "command",
213 "command": "bash check-path.sh", // Independent
214 "timeout": 2
215 },
216 {
217 "type": "prompt",
218 "prompt": "Check content safety", // Independent
219 "timeout": 10
220 }
221 ]
222 }
223 ]
224}
225```
226
227All three hooks run simultaneously, reducing total latency.
228
229## Cross-Event Workflows
230
231Coordinate hooks across different events:
232
233**SessionStart - Set up tracking:**
234```bash
235#!/bin/bash
236# Initialize session tracking
237echo "0" > /tmp/test-count-$$
238echo "0" > /tmp/build-count-$$
239```
240
241**PostToolUse - Track events:**
242```bash
243#!/bin/bash
244input=$(cat)
245tool_name=$(echo "$input" | jq -r '.tool_name')
246
247if [ "$tool_name" = "Bash" ]; then
248 command=$(echo "$input" | jq -r '.tool_result')
249 if [[ "$command" == *"test"* ]]; then
250 count=$(cat /tmp/test-count-$$ 2>/dev/null || echo "0")
251 echo $((count + 1)) > /tmp/test-count-$$
252 fi
253fi
254```
255
256**Stop - Verify based on tracking:**
257```bash
258#!/bin/bash
259test_count=$(cat /tmp/test-count-$$ 2>/dev/null || echo "0")
260
261if [ "$test_count" -eq 0 ]; then
262 echo '{"decision": "block", "reason": "No tests were run"}' >&2
263 exit 2
264fi
265```
266
267## Integration with External Systems
268
269### Slack Notifications
270
271```bash
272#!/bin/bash
273input=$(cat)
274tool_name=$(echo "$input" | jq -r '.tool_name')
275decision="blocked"
276
277# Send notification to Slack
278curl -X POST "$SLACK_WEBHOOK" \
279 -H 'Content-Type: application/json' \
280 -d "{\"text\": \"Hook ${decision} ${tool_name} operation\"}" \
281 2>/dev/null
282
283echo '{"decision": "deny"}' >&2
284exit 2
285```
286
287### Database Logging
288
289```bash
290#!/bin/bash
291input=$(cat)
292
293# Log to database
294psql "$DATABASE_URL" -c "INSERT INTO hook_logs (event, data) VALUES ('PreToolUse', '$input')" \
295 2>/dev/null
296
297exit 0
298```
299
300### Metrics Collection
301
302```bash
303#!/bin/bash
304input=$(cat)
305tool_name=$(echo "$input" | jq -r '.tool_name')
306
307# Send metrics to monitoring system
308echo "hook.pretooluse.${tool_name}:1|c" | nc -u -w1 statsd.local 8125
309
310exit 0
311```
312
313## Security Patterns
314
315### Rate Limiting
316
317```bash
318#!/bin/bash
319input=$(cat)
320command=$(echo "$input" | jq -r '.tool_input.command')
321
322# Track command frequency
323rate_file="/tmp/hook-rate-$$"
324current_minute=$(date +%Y%m%d%H%M)
325
326if [ -f "$rate_file" ]; then
327 last_minute=$(head -1 "$rate_file")
328 count=$(tail -1 "$rate_file")
329
330 if [ "$current_minute" = "$last_minute" ]; then
331 if [ "$count" -gt 10 ]; then
332 echo '{"decision": "deny", "reason": "Rate limit exceeded"}' >&2
333 exit 2
334 fi
335 count=$((count + 1))
336 else
337 count=1
338 fi
339else
340 count=1
341fi
342
343echo "$current_minute" > "$rate_file"
344echo "$count" >> "$rate_file"
345
346exit 0
347```
348
349### Audit Logging
350
351```bash
352#!/bin/bash
353input=$(cat)
354tool_name=$(echo "$input" | jq -r '.tool_name')
355timestamp=$(date -Iseconds)
356
357# Append to audit log
358echo "$timestamp | $USER | $tool_name | $input" >> ~/.claude/audit.log
359
360exit 0
361```
362
363### Secret Detection
364
365```bash
366#!/bin/bash
367input=$(cat)
368content=$(echo "$input" | jq -r '.tool_input.content')
369
370# Check for common secret patterns
371if echo "$content" | grep -qE "(api[_-]?key|password|secret|token).{0,20}['\"]?[A-Za-z0-9]{20,}"; then
372 echo '{"decision": "deny", "reason": "Potential secret detected in content"}' >&2
373 exit 2
374fi
375
376exit 0
377```
378
379## Testing Advanced Hooks
380
381### Unit Testing Hook Scripts
382
383```bash
384# test-hook.sh
385#!/bin/bash
386
387# Test 1: Approve safe command
388result=$(echo '{"tool_input": {"command": "ls"}}' | bash validate-bash.sh)
389if [ $? -eq 0 ]; then
390 echo "✓ Test 1 passed"
391else
392 echo "✗ Test 1 failed"
393fi
394
395# Test 2: Block dangerous command
396result=$(echo '{"tool_input": {"command": "rm -rf /"}}' | bash validate-bash.sh)
397if [ $? -eq 2 ]; then
398 echo "✓ Test 2 passed"
399else
400 echo "✗ Test 2 failed"
401fi
402```
403
404### Integration Testing
405
406Create test scenarios that exercise the full hook workflow:
407
408```bash
409# integration-test.sh
410#!/bin/bash
411
412# Set up test environment
413export CLAUDE_PROJECT_DIR="/tmp/test-project"
414export CLAUDE_PLUGIN_ROOT="$(pwd)"
415mkdir -p "$CLAUDE_PROJECT_DIR"
416
417# Test SessionStart hook
418echo '{}' | bash hooks/session-start.sh
419if [ -f "/tmp/session-initialized" ]; then
420 echo "✓ SessionStart hook works"
421else
422 echo "✗ SessionStart hook failed"
423fi
424
425# Clean up
426rm -rf "$CLAUDE_PROJECT_DIR"
427```
428
429## Best Practices for Advanced Hooks
430
4311. **Keep hooks independent**: Don't rely on execution order
4322. **Use timeouts**: Set appropriate limits for each hook type
4333. **Handle errors gracefully**: Provide clear error messages
4344. **Document complexity**: Explain advanced patterns in README
4355. **Test thoroughly**: Cover edge cases and failure modes
4366. **Monitor performance**: Track hook execution time
4377. **Version configuration**: Use version control for hook configs
4388. **Provide escape hatches**: Allow users to bypass hooks when needed
439
440## Common Pitfalls
441
442### ❌ Assuming Hook Order
443
444```bash
445# BAD: Assumes hooks run in specific order
446# Hook 1 saves state, Hook 2 reads it
447# This can fail because hooks run in parallel!
448```
449
450### ❌ Long-Running Hooks
451
452```bash
453# BAD: Hook takes 2 minutes to run
454sleep 120
455# This will timeout and block the workflow
456```
457
458### ❌ Uncaught Exceptions
459
460```bash
461# BAD: Script crashes on unexpected input
462file_path=$(echo "$input" | jq -r '.tool_input.file_path')
463cat "$file_path" # Fails if file doesn't exist
464```
465
466### ✅ Proper Error Handling
467
468```bash
469# GOOD: Handles errors gracefully
470file_path=$(echo "$input" | jq -r '.tool_input.file_path')
471if [ ! -f "$file_path" ]; then
472 echo '{"continue": true, "systemMessage": "File not found, skipping check"}' >&2
473 exit 0
474fi
475```
476
477## Conclusion
478
479Advanced hook patterns enable sophisticated automation while maintaining reliability and performance. Use these techniques when basic hooks are insufficient, but always prioritize simplicity and maintainability.