Làm chủ Claude Code trong một tuần (Phần 7): Hooks
Đây là phần thứ bảy trong series "Làm chủ Claude Code trong một tuần". Trong bài này, chúng ta sẽ tìm hiểu Hooks — event-driven automation cho Claude Code.
Hooks là gì?
Hooks là event-driven shell commands tự động execute khi Claude Code events xảy ra. Hooks cho phép bạn "hook into" lifecycle của Claude Code để thực hiện các actions như formatting, validation, notification.
┌────────────────────────────────────────────────────────────────┐
│ HOOKS │
├────────────────────────────────────────────────────────────────┤
│ │
│ Claude Code Event │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PreToolUse │────►│ format-code.sh │ │
│ │ (Write file) │ │ (Auto-format) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Tool executes │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PostToolUse │────►│ security-scan.sh│ │
│ │ (Write done) │ │ (Scan for issues)│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Hook Types
Claude Code có 4 loại hooks với 25 events:
1. Tool Hooks
| Event | Khi nào | Use case |
|---|---|---|
PreToolUse |
Trước khi tool chạy | Validation, formatting |
PostToolUse |
Sau khi tool hoàn thành | Scanning, logging |
PostToolUseFailure |
Khi tool fail | Error logging, recovery |
PermissionRequest |
Khi cần permission | Custom permission logic |
2. Session Hooks
| Event | Khi nào | Use case |
|---|---|---|
SessionStart |
Session bắt đầu | Setup, initialization |
SessionEnd |
Session kết thúc | Cleanup, summary |
Stop |
User stop | Save state |
StopFailure |
Stop fail | Recovery |
SubagentStart |
Subagent bắt đầu | Logging |
SubagentStop |
Subagent kết thúc | Cleanup |
3. Task Hooks
| Event | Khi nào | Use case |
|---|---|---|
UserPromptSubmit |
User gửi prompt | Validation, logging |
TaskCompleted |
Task hoàn thành | Notification |
TaskCreated |
Task mới tạo | Tracking |
TeammateIdle |
Teammate idle | Workload balance |
4. Lifecycle Hooks
| Event | Khi nào | Use case |
|---|---|---|
ConfigChange |
Config thay đổi | Reload |
CwdChanged |
Directory thay đổi | Context update |
FileChanged |
File thay đổi | Auto-refresh |
PreCompact |
Trước compact | Save important context |
PostCompact |
Sau compact | Restore context |
WorktreeCreate |
Worktree mới | Setup |
WorktreeRemove |
Worktree xóa | Cleanup |
Notification |
Notification | Custom handling |
InstructionsLoaded |
Instructions loaded | Validation |
Elicitation |
Claude asks question | Custom prompting |
ElicitationResult |
Answer received | Logging |
Cấu hình Hooks
settings.json Location
Global:
~/.claude/settings.json
Project:
.claude/settings.json
Cấu trúc cấu hình
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
],
"SessionStart": [
{
"hooks": ["~/.claude/hooks/session-start.sh"]
}
],
"TaskCompleted": [
{
"hooks": ["~/.claude/hooks/notify-team.sh"]
}
]
}
}
Matcher Options
| Matcher | Description | Example |
|---|---|---|
"Write" |
File write operations | Format before write |
"Read" |
File read operations | Log file access |
"Bash" |
Shell commands | Validate commands |
"*" |
All tools | Global logging |
"Edit" |
File edits | Backup before edit |
Cài đặt Hooks
Step 1: Tạo thư mục
mkdir -p ~/.claude/hooks
Step 2: Copy hooks từ claude-howto
cp claude-howto/06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh
Step 3: Cấu hình settings.json
# Tạo hoặc edit settings.json
cat > ~/.claude/settings.json << 'EOF'
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
]
}
}
EOF
Ví dụ Hooks
1. Auto-format Code
File: ~/.claude/hooks/format-code.sh
#!/bin/bash
# Auto-format code before writing
# Get file path from environment
FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Detect file type and format
case "$FILE_PATH" in
*.php)
# Format PHP with Pint or PHP-CS-Fixer
if command -v pint &> /dev/null; then
pint "$FILE_PATH" 2>/dev/null
elif command -v php-cs-fixer &> /dev/null; then
php-cs-fixer fix "$FILE_PATH" --quiet 2>/dev/null
fi
;;
*.js|*.ts|*.jsx|*.tsx)
# Format JavaScript/TypeScript with Prettier
if command -v prettier &> /dev/null; then
prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
*.py)
# Format Python with Black
if command -v black &> /dev/null; then
black "$FILE_PATH" 2>/dev/null
fi
;;
*.go)
# Format Go
if command -v gofmt &> /dev/null; then
gofmt -w "$FILE_PATH" 2>/dev/null
fi
;;
esac
exit 0
2. Security Scan
File: ~/.claude/hooks/security-scan.sh
#!/bin/bash
# Scan for security issues after file write
FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"
ISSUES_FOUND=0
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi
# Check for hardcoded secrets
check_secrets() {
local patterns=(
"password\s*=\s*['\"]"
"api_key\s*=\s*['\"]"
"secret\s*=\s*['\"]"
"AWS_ACCESS_KEY"
"PRIVATE_KEY"
)
for pattern in "${patterns[@]}"; do
if grep -iE "$pattern" "$FILE_PATH" > /dev/null 2>&1; then
echo "⚠️ Potential hardcoded secret found: $pattern"
ISSUES_FOUND=1
fi
done
}
# Check for SQL injection (basic)
check_sql_injection() {
# Check for string concatenation in SQL
if grep -E "(SELECT|INSERT|UPDATE|DELETE).*\\\$" "$FILE_PATH" > /dev/null 2>&1; then
echo "⚠️ Potential SQL injection: variable in SQL string"
ISSUES_FOUND=1
fi
}
# Check for debug statements
check_debug() {
local debug_patterns=(
"console\.log"
"var_dump"
"print_r"
"dd\("
"dump\("
"debugger;"
)
for pattern in "${debug_patterns[@]}"; do
if grep -E "$pattern" "$FILE_PATH" > /dev/null 2>&1; then
echo "⚠️ Debug statement found: $pattern"
ISSUES_FOUND=1
fi
done
}
# Run checks
check_secrets
check_sql_injection
check_debug
if [ $ISSUES_FOUND -eq 1 ]; then
echo ""
echo "Security scan found issues in: $FILE_PATH"
echo "Please review before committing."
fi
exit 0
3. Log Bash Commands
File: ~/.claude/hooks/log-bash.sh
#!/bin/bash
# Log all bash commands executed by Claude
LOG_FILE="${HOME}/.claude/logs/bash-$(date +%Y-%m-%d).log"
mkdir -p "$(dirname "$LOG_FILE")"
# Get command from environment
COMMAND="$CLAUDE_TOOL_INPUT_COMMAND"
if [ -n "$COMMAND" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $COMMAND" >> "$LOG_FILE"
fi
exit 0
Cấu hình:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": ["~/.claude/hooks/log-bash.sh"]
}
]
}
}
4. Pre-commit Validation
File: ~/.claude/hooks/pre-commit.sh
#!/bin/bash
# Run tests before committing
# Only run for git operations
if [[ "$CLAUDE_TOOL_INPUT_COMMAND" != *"git commit"* ]]; then
exit 0
fi
echo "🧪 Running pre-commit checks..."
# Run tests
if [ -f "phpunit.xml" ]; then
./vendor/bin/phpunit --stop-on-failure
if [ $? -ne 0 ]; then
echo "❌ Tests failed. Commit blocked."
exit 1
fi
elif [ -f "package.json" ]; then
npm test
if [ $? -ne 0 ]; then
echo "❌ Tests failed. Commit blocked."
exit 1
fi
fi
echo "✅ Pre-commit checks passed."
exit 0
5. Team Notification
File: ~/.claude/hooks/notify-team.sh
#!/bin/bash
# Notify team when task is completed
WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
if [ -z "$WEBHOOK_URL" ]; then
exit 0
fi
# Get task info
TASK_NAME="${CLAUDE_TASK_NAME:-Unknown task}"
PROJECT_DIR="$(pwd)"
PROJECT_NAME="$(basename "$PROJECT_DIR")"
# Send Slack notification
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"✅ Task completed in $PROJECT_NAME\",
\"blocks\": [
{
\"type\": \"section\",
\"text\": {
\"type\": \"mrkdwn\",
\"text\": \"*Task Completed*\n$TASK_NAME\"
}
},
{
\"type\": \"context\",
\"elements\": [
{
\"type\": \"mrkdwn\",
\"text\": \"Project: $PROJECT_NAME\"
}
]
}
]
}" > /dev/null
exit 0
Cấu hình:
{
"hooks": {
"TaskCompleted": [
{
"hooks": ["~/.claude/hooks/notify-team.sh"]
}
]
}
}
6. Validate User Prompt
File: ~/.claude/hooks/validate-prompt.sh
#!/bin/bash
# Validate user prompts before processing
PROMPT="$CLAUDE_USER_PROMPT"
# Check for dangerous patterns
if echo "$PROMPT" | grep -iE "(delete all|rm -rf|drop database|format disk)" > /dev/null; then
echo "⚠️ Warning: Potentially dangerous operation requested."
echo "Please confirm you want to proceed with: $PROMPT"
# Could integrate with approval system here
fi
# Check for prohibited actions in production
if [ "${ENVIRONMENT:-}" = "production" ]; then
if echo "$PROMPT" | grep -iE "(deploy|migrate|seed)" > /dev/null; then
echo "🚫 Production environment detected."
echo "Direct deployment/migration commands are blocked."
echo "Please use the proper CI/CD pipeline."
exit 1
fi
fi
exit 0
Environment Variables
Hooks nhận thông tin qua environment variables:
Tool Hooks
| Variable | Description |
|---|---|
CLAUDE_TOOL_NAME |
Tên tool đang chạy |
CLAUDE_TOOL_INPUT_PATH |
File path (cho file operations) |
CLAUDE_TOOL_INPUT_COMMAND |
Command (cho bash) |
CLAUDE_TOOL_OUTPUT |
Tool output (PostToolUse) |
Session Hooks
| Variable | Description |
|---|---|
CLAUDE_SESSION_ID |
Session ID |
CLAUDE_SESSION_START |
Start timestamp |
CLAUDE_PROJECT_DIR |
Project directory |
Task Hooks
| Variable | Description |
|---|---|
CLAUDE_TASK_NAME |
Task description |
CLAUDE_TASK_ID |
Task ID |
CLAUDE_USER_PROMPT |
User's prompt |
Best Practices
1. Exit codes matter
# ✅ Good - Use exit codes properly
if [ -f "$FILE" ]; then
do_something
exit 0 # Success
else
echo "File not found"
exit 1 # Failure - may block operation
fi
2. Keep hooks fast
# ❌ Bad - Slow hook
npm install # Don't do heavy operations
# ✅ Good - Quick check
if [ ! -d "node_modules" ]; then
echo "⚠️ Dependencies not installed. Run: npm install"
fi
3. Fail gracefully
#!/bin/bash
# ✅ Good - Handle missing tools
if ! command -v prettier &> /dev/null; then
# Prettier not installed, skip formatting
exit 0
fi
prettier --write "$FILE_PATH"
4. Use logging
#!/bin/bash
LOG_FILE="${HOME}/.claude/logs/hooks.log"
mkdir -p "$(dirname "$LOG_FILE")"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
log "Starting security scan for $FILE_PATH"
# ... hook logic
log "Security scan completed"
5. Conditional execution
#!/bin/bash
# ✅ Good - Only run for specific files
case "$FILE_PATH" in
*.php|*.js|*.ts)
# Run for code files
run_security_scan
;;
*.md|*.txt)
# Skip for documentation
exit 0
;;
esac
Debugging Hooks
Enable verbose logging
{
"hooks": {
"debug": true,
"PreToolUse": [...]
}
}
Test hook manually
# Simulate environment
export CLAUDE_TOOL_INPUT_PATH="/path/to/file.php"
export CLAUDE_TOOL_NAME="Write"
# Run hook
bash ~/.claude/hooks/format-code.sh
# Check exit code
echo "Exit code: $?"
Check logs
# View hook execution logs
tail -f ~/.claude/logs/hooks.log
# View Claude Code logs
tail -f ~/.claude/logs/claude-code.log
Cấu hình hoàn chỉnh
{
"hooks": {
"debug": false,
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
"~/.claude/hooks/format-code.sh",
"~/.claude/hooks/backup-file.sh"
]
},
{
"matcher": "Bash",
"hooks": ["~/.claude/hooks/log-bash.sh"]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": ["~/.claude/hooks/security-scan.sh"]
}
],
"UserPromptSubmit": [
{
"hooks": ["~/.claude/hooks/validate-prompt.sh"]
}
],
"SessionStart": [
{
"hooks": ["~/.claude/hooks/session-start.sh"]
}
],
"TaskCompleted": [
{
"hooks": ["~/.claude/hooks/notify-team.sh"]
}
]
}
}
Tổng kết
Hooks mang lại automation mạnh mẽ:
- ✅ Event-driven execution
- ✅ 25 hook events
- ✅ Custom validation và formatting
- ✅ Integration với external tools
- ✅ Team notifications
Tiếp theo
Trong phần tiếp theo, chúng ta sẽ tìm hiểu MCP Protocol — cách kết nối Claude Code với external tools và APIs.
Tài liệu tham khảo
Series này được dịch và mở rộng từ claude-howto — MIT License.