Bỏ qua để đến nội dung

Hệ thống Hook

Thời gian học: ~30 phút

Yêu cầu trước: Module 11.2 (SDK Integration)

Kết quả: Sau module này, bạn sẽ hiểu hook concept, biết common pattern, và implement custom hook cho workflow của mình.


Claude Code write file → bạn muốn intercept thêm logic: lint check, audit log, Slack notify, block sensitive files. Hook cho phép tự động intercept code tại moment cần thiết. Ví von: Pre-hook = kiểm tra trước vào. Post-hook = ghi log sau. Không hook = phải manual post-process.


Hook là custom code chạy tại specific event trong Claude Code lifecycle. Giống Git hooks, middleware, hoặc lifecycle methods.

Hook TypeThời điểm chạyCó block được?Use Case
Pre-hookTrước action✅ Exit 1 = blockValidation, lint, backup
Post-hookSau action❌ Không undoLogging, notification, cleanup

Các event có thể có trong hook system:

EventKhi nào triggerTypical Use Case
pre-file-writeTrước khi write fileLint check, format validation
post-file-writeSau khi write fileAudit log, git add, notify
pre-commandTrước execute commandSecurity check, approval gate
post-commandSau command xongLog result, metrics
post-sessionSession endSummary report, cleanup, Slack

Hook nhận context via env vars: CLAUDE_EVENT, CLAUDE_FILE_PATH, CLAUDE_SESSION_ID, CLAUDE_USER. Exit code: 0=allow, 1=block (pre-hook only).

graph TB
A[Claude Code action] --> B{Pre-hook exists?}
B -->|Yes| C[Execute pre-hook]
B -->|No| E[Execute action]
C --> D{Exit code?}
D -->|0| E
D -->|1| F[Block action - show error]
E --> G{Post-hook exists?}
G -->|Yes| H[Execute post-hook]
G -->|No| I[Done]
H --> I

Hook config trong JSON file hooks.json: { "hooks": { "event": "./path/script.sh" } }


Scenario: Team setup hook cho workflow gồm lint check, audit log, và Slack notification.

Terminal window
mkdir -p ~/claude-projects/my-app/.claude/hooks
cd ~/claude-projects/my-app/.claude/hooks

Tạo audit-log.sh — log mọi file change:

cat > audit-log.sh << 'EOF'
#!/bin/bash
# Post-hook: Audit log for file changes
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
LOG_FILE=".claude/audit.log"
# Create JSON log entry
echo "{
\"timestamp\": \"$TIMESTAMP\",
\"event\": \"$CLAUDE_EVENT\",
\"file\": \"$CLAUDE_FILE_PATH\",
\"session\": \"$CLAUDE_SESSION_ID\",
\"user\": \"$CLAUDE_USER\"
}" >> "$LOG_FILE"
echo "✓ Logged change to $LOG_FILE"
exit 0
EOF
chmod +x audit-log.sh
cat > lint-check.sh << 'EOF'
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
if [[ "$FILE" =~ \.(ts|js)$ ]]; then
npx eslint "$FILE" --quiet && exit 0
echo "❌ Lint check failed - blocking write"
exit 1
fi
exit 0
EOF
chmod +x lint-check.sh

Tạo notify-slack.sh — gửi summary khi session end:

cat > notify-slack.sh << 'EOF'
#!/bin/bash
# Post-hook: Slack notification
AUDIT_COUNT=$(wc -l < .claude/audit.log 2>/dev/null || echo 0)
curl -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\": \"Claude session done. Files: $AUDIT_COUNT\"}" \
--silent
exit 0
EOF
chmod +x notify-slack.sh

Expected output:

# File created and made executable

Tạo hooks.json:

Terminal window
cat > ../hooks.json << 'EOF'
{
"hooks": {
"pre-file-write": ".claude/hooks/lint-check.sh",
"post-file-write": ".claude/hooks/audit-log.sh",
"post-session": ".claude/hooks/notify-slack.sh"
}
}
EOF

Test the hooks:

Write file qua Claude Code & verify hooks trigger:

Terminal window
claude -p "Add error handling to src/utils.ts"
# Expected: lint check runs, then audit log created, Slack sent
cat .claude/audit.log # Verify log entries exist

Goal: Tạo post-file-write hook log change với format CSV cho Excel import

Instructions:

  1. Tạo audit-csv.sh trong .claude/hooks/
  2. Format: timestamp,file,user
  3. Append vào .claude/audit.csv
  4. Test bằng cách cho Claude write file

Expected result: File .claude/audit.csv có entry mới mỗi lần write

💡 Hint

Dùng date, echo với >>, CSV format đơn giản.

✅ Solution
audit-csv.sh
#!/bin/bash
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
CSV_FILE=".claude/audit.csv"
# Create header if file doesn't exist
if [[ ! -f "$CSV_FILE" ]]; then
echo "timestamp,file,user" > "$CSV_FILE"
fi
# Append entry
echo "$TIMESTAMP,$CLAUDE_FILE_PATH,$CLAUDE_USER" >> "$CSV_FILE"
echo "✓ Logged to CSV"
exit 0

Đừng quên chmod +x và add vào hooks.json.


Goal: Tạo pre-file-write hook block write vào sensitive files

Instructions:

  1. Tạo protect-sensitive.sh
  2. Block write vào .env, *.key, config/production.json
  3. Show error message khi block
  4. Test bằng cách try write vào .env

Expected result: Write vào .env bị block với clear error message

💡 Hint

Pattern matching [[ "$FILE" =~ pattern ]], exit 1 để block.

✅ Solution
protect-sensitive.sh
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
# Check sensitive patterns
if [[ "$FILE" == ".env" ]] || \
[[ "$FILE" =~ \.key$ ]] || \
[[ "$FILE" == "config/production.json" ]]; then
echo "❌ BLOCKED: Cannot modify sensitive file"
echo "File: $FILE"
echo "Manual review required for production changes"
exit 1
fi
# Allow all other files
exit 0

Add vào hooks.json:

{
"hooks": {
"pre-file-write": ".claude/hooks/protect-sensitive.sh"
}
}

Goal: Tạo post-session hook gửi summary qua Discord webhook

Instructions:

  1. Tạo notify-discord.sh
  2. Count files changed từ audit log
  3. Send Discord webhook với embed format
  4. Test sau khi end session

Expected result: Discord message với summary sau session

💡 Hint

Discord dùng embeds array khác Slack text format.

✅ Solution
notify-discord.sh
#!/bin/bash
AUDIT_COUNT=$(wc -l < .claude/audit.log 2>/dev/null || echo 0)
PAYLOAD="{\"content\": \"Claude session done - Files: $AUDIT_COUNT, User: $CLAUDE_USER\"}"
curl -X POST "$DISCORD_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$PAYLOAD" \
--silent
exit 0

Set environment variable:

Terminal window
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."

TypeTimingCan Block?Common Use
Pre-hookBefore action✅ Yes (exit 1)Validation, lint, approval
Post-hookAfter action❌ NoLogging, notification, metrics
#!/bin/bash
# Hook: [event-name]
# Available env vars:
# - CLAUDE_EVENT
# - CLAUDE_FILE_PATH (file hooks)
# - CLAUDE_SESSION_ID
# - CLAUDE_USER
# Your logic here
# Pre-hook exit codes:
exit 0 # Allow action
exit 1 # Block action
# Post-hook:
exit 0 # Success
{
"hooks": {
"event-name": "path/to/script.sh"
},
"hookTimeout": 30000
}

Config location: .claude/hooks.json (⚠️ Cần xác minh)

Terminal window
# Make hook executable
chmod +x .claude/hooks/my-hook.sh
# Test hook manually
CLAUDE_EVENT=post-file-write \
CLAUDE_FILE_PATH=test.ts \
.claude/hooks/my-hook.sh
# Check hook exit code
echo $? # 0 = success, 1 = blocked/failed
# Debug hook
bash -x .claude/hooks/my-hook.sh # Show execution trace

❌ Mistake✅ Correct Approach
Quên chmod +xAlways chmod +x sau tạo script
Dùng exit 1 trong post-hookPost-hook chạy sau — không undo. Dùng pre-hook block.
Hook chạy slowKeep <500ms, dùng async cho slow ops
Hardcode pathDùng env variables, portable
Hook fail silentCheck exit code, add debug echo
Không test standaloneTest manual với mock env vars trước

Scenario: Fintech company Việt Nam cần audit trail cho mọi code change bởi AI. Compliance regulation mandate log WHO changed WHAT WHEN và WHY.

Problem:

  • Dev dùng Claude Code viết code
  • Compliance team cần audit log export mỗi tháng
  • Manual logging không practical — dev quên hoặc bỏ qua

Solution: Hook-based audit system

Implementation:

#!/bin/bash
# .claude/hooks/audit-log.sh — Key parts only
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
LOG_ENTRY="{\"timestamp\": \"$TIMESTAMP\", \"file\": \"$CLAUDE_FILE_PATH\", \"user\": \"$CLAUDE_USER\"}"
echo "$LOG_ENTRY" >> /var/log/claude-audit/audit.jsonl
# Async send to central service
(curl -X POST https://logs.company.internal/audit -d "$LOG_ENTRY" --silent &) &
exit 0

Hook config:

{
"hooks": {
"post-file-write": ".claude/hooks/audit-log.sh",
"post-command": ".claude/hooks/audit-log.sh"
}
}

Result: 100% audit coverage, zero manual logging, compliance export automated, dev workflow unaffected.


Tiếp theo: Module 11.4: GitHub Actions Integration