Files
gh-glittercowboy-taches-cc-…/skills/create-hooks/references/examples.md
2025-11-29 18:28:37 +08:00

659 lines
12 KiB
Markdown

# Working Examples
Real-world hook configurations ready to use.
## Desktop Notifications
### macOS notification when input needed
```json
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude needs your input\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}
```
### Linux notification (notify-send)
```json
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Awaiting your input' --urgency=normal"
}
]
}
]
}
}
```
### Play sound on notification
```json
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
```
---
## Logging
### Log all bash commands
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '\"[\" + (.timestamp // now | todate) + \"] \" + .tool_input.command + \" - \" + (.tool_input.description // \"No description\")' >> ~/.claude/bash-log.txt"
}
]
}
]
}
}
```
### Log file operations
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '\"[\" + (now | todate) + \"] \" + .tool_name + \": \" + .tool_input.file_path' >> ~/.claude/file-operations.log"
}
]
}
]
}
}
```
### Audit trail for MCP operations
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__.*",
"hooks": [
{
"type": "command",
"command": "jq '. + {timestamp: now}' >> ~/.claude/mcp-audit.jsonl"
}
]
}
]
}
}
```
---
## Code Quality
### Auto-format after edits
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$(echo {} | jq -r '.tool_input.file_path')\" 2>/dev/null || true",
"timeout": 10000
}
]
}
]
}
}
```
### Run linter after code changes
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "eslint \"$(echo {} | jq -r '.tool_input.file_path')\" --fix 2>/dev/null || true"
}
]
}
]
}
}
```
### Run tests before stopping
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/check-tests.sh"
}
]
}
]
}
}
```
`check-tests.sh`:
```bash
#!/bin/bash
cd "$cwd" || exit 1
# Run tests
npm test > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo '{"decision": "approve", "reason": "All tests passing"}'
else
echo '{"decision": "block", "reason": "Tests are failing. Please fix before stopping.", "systemMessage": "Run npm test to see failures"}'
fi
```
---
## Safety and Validation
### Block destructive commands
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/check-command-safety.sh"
}
]
}
]
}
}
```
`check-command-safety.sh`:
```bash
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
# Check for dangerous patterns
if [[ "$command" == *"rm -rf /"* ]] || \
[[ "$command" == *"mkfs"* ]] || \
[[ "$command" == *"> /dev/sda"* ]]; then
echo '{"decision": "block", "reason": "Destructive command detected", "systemMessage": "This command could cause data loss"}'
exit 0
fi
# Check for force push to main
if [[ "$command" == *"git push"*"--force"* ]] && \
[[ "$command" == *"main"* || "$command" == *"master"* ]]; then
echo '{"decision": "block", "reason": "Force push to main branch blocked", "systemMessage": "Use a feature branch instead"}'
exit 0
fi
echo '{"decision": "approve", "reason": "Command is safe"}'
```
### Validate commit messages
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Check if this is a git commit command: $ARGUMENTS\n\nIf it's a git commit, validate the message follows conventional commits format (feat|fix|docs|refactor|test|chore): description\n\nIf invalid format: {\"decision\": \"block\", \"reason\": \"Commit message must follow conventional commits\"}\nIf valid or not a commit: {\"decision\": \"approve\", \"reason\": \"ok\"}"
}
]
}
]
}
}
```
### Block writes to critical files
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/check-protected-files.sh"
}
]
}
]
}
}
```
`check-protected-files.sh`:
```bash
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# Protected files
protected_files=(
"package-lock.json"
".env.production"
"credentials.json"
)
for protected in "${protected_files[@]}"; do
if [[ "$file_path" == *"$protected"* ]]; then
echo "{\"decision\": \"block\", \"reason\": \"Cannot modify $protected\", \"systemMessage\": \"This file is protected from automated changes\"}"
exit 0
fi
done
echo '{"decision": "approve", "reason": "File is not protected"}'
```
---
## Context Injection
### Load sprint context at session start
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/load-sprint-context.sh"
}
]
}
]
}
}
```
`load-sprint-context.sh`:
```bash
#!/bin/bash
# Read sprint info from file
sprint_info=$(cat "$CLAUDE_PROJECT_DIR/.sprint-context.txt" 2>/dev/null || echo "No sprint context available")
# Return as SessionStart context
jq -n \
--arg context "$sprint_info" \
'{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": $context
}
}'
```
### Load git branch context
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$cwd\" && git branch --show-current | jq -Rs '{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": (\"Current branch: \" + .)}}'"
}
]
}
]
}
}
```
### Load environment info
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": \"Environment: '$(hostname)'\\nNode version: '$(node --version 2>/dev/null || echo 'not installed')'\\nPython version: '$(python3 --version 2>/dev/null || echo 'not installed)'\"}}'"
}
]
}
]
}
}
```
---
## Workflow Automation
### Auto-commit after major changes
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/auto-commit.sh"
}
]
}
]
}
}
```
`auto-commit.sh`:
```bash
#!/bin/bash
cd "$cwd" || exit 1
# Check if there are changes
if ! git diff --quiet; then
git add -A
git commit -m "chore: auto-commit from claude session" --no-verify
echo '{"systemMessage": "Changes auto-committed"}'
fi
```
### Update documentation after code changes
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/update-docs.sh",
"timeout": 30000
}
]
}
]
}
}
```
### Run pre-commit hooks
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/check-pre-commit.sh"
}
]
}
]
}
}
```
`check-pre-commit.sh`:
```bash
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
# If git commit, run pre-commit hooks first
if [[ "$command" == *"git commit"* ]]; then
pre-commit run --all-files > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo '{"decision": "block", "reason": "Pre-commit hooks failed", "systemMessage": "Fix formatting/linting issues first"}'
exit 0
fi
fi
echo '{"decision": "approve", "reason": "ok"}'
```
---
## Session Management
### Archive transcript on session end
```json
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/archive-session.sh"
}
]
}
]
}
}
```
`archive-session.sh`:
```bash
#!/bin/bash
input=$(cat)
transcript_path=$(echo "$input" | jq -r '.transcript_path')
session_id=$(echo "$input" | jq -r '.session_id')
# Create archive directory
archive_dir="$HOME/.claude/archives"
mkdir -p "$archive_dir"
# Copy transcript with timestamp
timestamp=$(date +%Y%m%d-%H%M%S)
cp "$transcript_path" "$archive_dir/${timestamp}-${session_id}.jsonl"
echo "Session archived to $archive_dir"
```
### Save session stats
```json
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "jq '. + {ended_at: now}' >> ~/.claude/session-stats.jsonl"
}
]
}
]
}
}
```
---
## Advanced Patterns
### Intelligent stop logic
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Review the conversation: $ARGUMENTS\n\nCheck if:\n1. All user-requested tasks are complete\n2. Tests are passing (if code changes made)\n3. No errors that need fixing\n4. Documentation updated (if applicable)\n\nIf incomplete: {\"decision\": \"block\", \"reason\": \"specific issue\", \"systemMessage\": \"what needs to be done\"}\n\nIf complete: {\"decision\": \"approve\", \"reason\": \"all tasks done\"}\n\nIMPORTANT: If stop_hook_active is true, return {\"decision\": undefined} to avoid infinite loop",
"timeout": 30000
}
]
}
]
}
}
```
### Chain multiple hooks
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'First hook' >> /tmp/hook-chain.log"
},
{
"type": "command",
"command": "echo 'Second hook' >> /tmp/hook-chain.log"
},
{
"type": "prompt",
"prompt": "Final validation: $ARGUMENTS"
}
]
}
]
}
}
```
Hooks execute in order. First block stops the chain.
### Conditional execution based on file type
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/format-by-type.sh"
}
]
}
]
}
}
```
`format-by-type.sh`:
```bash
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
case "$file_path" in
*.js|*.jsx|*.ts|*.tsx)
prettier --write "$file_path"
;;
*.py)
black "$file_path"
;;
*.go)
gofmt -w "$file_path"
;;
esac
```
---
## Project-Specific Hooks
Use `$CLAUDE_PROJECT_DIR` for project-specific hooks:
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/init-session.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-changes.sh"
}
]
}
]
}
}
```
This keeps hook scripts versioned with the project.