Claude Code Hooks — Tự động hóa workflow với pre/post hooks
Điểm nổi bật
Nhấn để đến mục tương ứng
- 1 Nhược điểm: chỉ trigger khi commit/push, không theo dõi file write Claude Code hooks: Trigger chính xác khi Claude dùng tool.
- 2 Quan trọng nhất là hooks phải giúp bạn làm việc nhanh hơn chứ không chậm hơn — nếu hook chạy quá lâu hoặc hay gây lỗi giả, hãy điều chỉnh hoặc gỡ bỏ.
- 3 Nhược điểm: Claude có thể quên, không đảm bảo 100% Git hooks: Pre-commit, pre-push hooks truyền thống.
- 4 Bước tiếp theo Hooks là công cụ mạnh mẽ biến Claude Code thành một phần không thể tách rời trong quy trình phát triển.
- 5 Hooks giải quyết vấn đề này bằng cách tự động hóa — đảm bảo các bước quan trọng không bao giờ bị bỏ qua.
Hooks trong Claude Code là cơ chế cho phép bạn chạy các lệnh shell tự động trước hoặc sau khi Claude thực hiện một hành động cụ thể. Tương tự như git hooks cho phép chạy script trước/sau commit, Claude Code hooks cho phép bạn tự động hóa các bước trong workflow phát triển — từ format code, chạy linter, thực thi test đến gửi thông báo. Đây là tính năng nâng cao giúp biến Claude Code từ một công cụ hỗ trợ thành một phần tích hợp chặt chẽ trong quy trình phát triển phần mềm của bạn.
Hooks là gì và tại sao cần chúng?
Khi làm việc với Claude Code, có những tác vụ bạn muốn xảy ra tự động mỗi khi Claude thực hiện một hành động nhất định. Ví dụ:
- Tự động chạy Prettier hoặc ESLint sau khi Claude sửa file JavaScript
- Chạy unit test sau khi Claude thay đổi code trong thư mục src/
- Gửi thông báo đến Slack khi Claude hoàn thành một tác vụ lớn
- Kiểm tra type safety với TypeScript compiler trước khi Claude commit
- Tự động backup file trước khi Claude ghi đè
Không có hooks, bạn phải nhớ chạy từng lệnh thủ công hoặc nhắc Claude làm mỗi lần. Hooks giải quyết vấn đề này bằng cách tự động hóa — đảm bảo các bước quan trọng không bao giờ bị bỏ qua.
Ba loại hooks trong Claude Code
Claude Code hỗ trợ ba loại hooks, mỗi loại kích hoạt tại thời điểm khác nhau trong quá trình thực thi:
- PreToolUse: Chạy trước khi Claude sử dụng một tool cụ thể (ví dụ: trước khi ghi file, trước khi chạy lệnh bash). Có thể chặn hành động nếu hook thất bại
- PostToolUse: Chạy sau khi Claude sử dụng tool thành công. Phù hợp cho validation, formatting, testing
- Notification: Chạy khi Claude gửi thông báo (ví dụ: khi cần input từ người dùng, khi hoàn thành tác vụ). Phù hợp cho tích hợp với hệ thống thông báo
Cấu hình hooks trong settings.json
Hooks được cấu hình trong file settings.json của Claude Code. File này có thể nằm ở cấp project hoặc cấp user:
- Project-level: .claude/settings.json trong thư mục dự án — áp dụng cho tất cả người dùng làm việc trên project
- User-level: ~/.claude/settings.json — áp dụng cho mọi project của bạn
Cấu trúc cơ bản của hooks
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'About to write a file'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $CLAUDE_FILE_PATH"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "echo '$CLAUDE_NOTIFICATION' >> ~/claude-notifications.log"
}
]
}
]
}
}
Giải thích cấu trúc:
- matcher: Tên tool cần hook vào. Các tool phổ biến: Write (ghi file), Bash (chạy lệnh), Read (đọc file). Để trống "" để match tất cả tools
- hooks: Mảng các hook cần chạy. Mỗi hook có type ("command") và command (lệnh shell cần thực thi)
- Biến môi trường: Claude Code truyền các biến như $CLAUDE_FILE_PATH, $CLAUDE_NOTIFICATION vào hook command
Các biến môi trường có sẵn trong hooks
Khi hook chạy, Claude Code cung cấp thông tin ngữ cảnh qua biến môi trường. Dưới đây là các biến quan trọng nhất:
- $CLAUDE_FILE_PATH: Đường dẫn file đang được thao tác (với Write, Read tools)
- $CLAUDE_TOOL_NAME: Tên tool đang được sử dụng
- $CLAUDE_TOOL_INPUT: Input JSON đầy đủ của tool (chứa tất cả tham số)
- $CLAUDE_NOTIFICATION: Nội dung thông báo (với Notification hooks)
- $CLAUDE_PROJECT_DIR: Thư mục gốc của project
Use case 1: Auto-format code sau khi Claude viết file
Đây là use case phổ biến nhất. Claude viết code theo style riêng, nhưng project của bạn có quy chuẩn format nhất định qua Prettier, Black, gofmt hoặc công cụ tương tự.
Cấu hình Prettier cho JavaScript/TypeScript
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.js|*.ts|*.jsx|*.tsx|*.json|*.css) npx prettier --write "$CLAUDE_FILE_PATH" ;; esac"
}
]
}
]
}
}
Hook này kiểm tra extension file — chỉ chạy Prettier cho các file JavaScript, TypeScript, JSON và CSS. Các file khác sẽ bị bỏ qua.
Cấu hình cho dự án Python
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.py) black "$CLAUDE_FILE_PATH" && isort "$CLAUDE_FILE_PATH" ;; esac"
}
]
}
]
}
}
Use case 2: Lint trước khi commit
Bạn có thể kết hợp hooks để đảm bảo code luôn qua lint trước khi Claude tạo commit.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if echo "$CLAUDE_TOOL_INPUT" | grep -q 'git commit'; then npx eslint src/ --max-warnings 0 || (echo 'ESLint failed. Fix errors before committing.' && exit 1); fi"
}
]
}
]
}
}
Hook này kiểm tra xem Claude có đang cố chạy git commit không. Nếu có, ESLint sẽ chạy trước. Nếu ESLint phát hiện lỗi, hook trả về exit code 1 và Claude sẽ không thực hiện commit.
Use case 3: Tự động chạy test sau khi sửa code
Để đảm bảo Claude không vô tình phá vỡ functionality hiện có, bạn có thể cấu hình hook chạy test sau mỗi lần sửa file trong thư mục src/.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if echo "$CLAUDE_FILE_PATH" | grep -q 'src/'; then npm test -- --bail --silent 2>&1 | tail -20; fi"
}
]
}
]
}
}
Lưu ý: Hook này chạy toàn bộ test suite, có thể chậm với project lớn. Trong thực tế, bạn có thể viết script thông minh hơn để chỉ chạy test liên quan đến file đã thay đổi.
Script chạy test thông minh
Tạo file .claude/scripts/smart-test.sh:
#!/bin/bash
# Tìm file test tương ứng với file source đã thay đổi
SOURCE_FILE="$1"
TEST_FILE=$(echo "$SOURCE_FILE" | sed 's/src//test//' | sed 's/.ts$/.test.ts/')
if [ -f "$TEST_FILE" ]; then
npx jest "$TEST_FILE" --bail --silent 2>&1 | tail -10
else
echo "No corresponding test file found for $SOURCE_FILE"
fi
Sau đó cấu hình hook:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if echo "$CLAUDE_FILE_PATH" | grep -q 'src/'; then bash .claude/scripts/smart-test.sh "$CLAUDE_FILE_PATH"; fi"
}
]
}
]
}
}
Use case 4: Thông báo khi hoàn thành
Khi Claude chạy tác vụ dài (refactor lớn, migration), bạn có thể muốn nhận thông báo khi hoàn thành.
Thông báo qua macOS notification
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification "$CLAUDE_NOTIFICATION" with title "Claude Code"'"
}
]
}
]
}
}
Thông báo qua Slack webhook
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST -H 'Content-type: application/json' --data '{"text":"Claude Code: '"$CLAUDE_NOTIFICATION"'"}' $SLACK_WEBHOOK_URL"
}
]
}
]
}
}
Bảo mật và các lưu ý quan trọng
Hooks chạy lệnh shell với quyền của user hiện tại, nên cần lưu ý về bảo mật:
Nguyên tắc bảo mật
- Không hardcode secrets: Sử dụng biến môi trường cho API keys, webhook URLs. Không commit secrets vào settings.json
- Validate input: Biến $CLAUDE_TOOL_INPUT và $CLAUDE_FILE_PATH có thể chứa ký tự đặc biệt. Luôn đặt trong dấu ngoặc kép
- Giới hạn phạm vi: Sử dụng điều kiện (if/case) để hook chỉ chạy trên file/thư mục cần thiết, tránh ảnh hưởng không mong muốn
- Kiểm tra exit code: PreToolUse hooks nên trả về exit code rõ ràng. Exit 0 = cho phép tiếp tục, exit khác 0 = chặn hành động
- Timeout: Hooks có giới hạn thời gian chạy. Nếu hook chạy quá lâu (ví dụ: full test suite trên project lớn), nó có thể bị kill
Lưu ý về project-level settings
// .claude/settings.json (project-level)
// File này được commit vào repo, mọi người trong team đều dùng
// CHỈ đặt hooks phù hợp cho cả team
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true"
}
]
}
]
}
}
// Lưu ý: thêm "|| true" để hook không block nếu prettier
// chưa được install trên máy đồng nghiệp
Debugging hooks
Khi hook không hoạt động như mong đợi, có một số cách để debug:
Ghi log để debug
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo "[$(date)] Tool: $CLAUDE_TOOL_NAME, File: $CLAUDE_FILE_PATH" >> /tmp/claude-hooks.log && npx prettier --write "$CLAUDE_FILE_PATH" 2>&1 >> /tmp/claude-hooks.log"
}
]
}
]
}
}
Sau đó kiểm tra log:
tail -f /tmp/claude-hooks.log
Các lỗi thường gặp và cách xử lý
- Hook không chạy: Kiểm tra matcher có đúng tên tool không. Tên tool phân biệt hoa thường — "Write" chứ không phải "write"
- Command not found: Hook chạy trong shell mới, có thể không có PATH đầy đủ. Dùng đường dẫn tuyệt đối cho các lệnh hoặc đảm bảo PATH được thiết lập
- Hook chạy nhưng không có hiệu lực: Kiểm tra xem file có nằm trong phạm vi điều kiện if/case không. Thêm echo debug để xác nhận
- PreToolUse hook không chặn được hành động: Đảm bảo hook trả về exit code khác 0 khi muốn chặn. Dùng "exit 1" rõ ràng
Kết hợp nhiều hooks
Bạn có thể kết hợp nhiều hooks cho cùng một tool để tạo pipeline hoàn chỉnh:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.ts|*.tsx) npx prettier --write "$CLAUDE_FILE_PATH" ;; esac"
},
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.ts|*.tsx) npx eslint --fix "$CLAUDE_FILE_PATH" 2>/dev/null || true ;; esac"
},
{
"type": "command",
"command": "if echo "$CLAUDE_FILE_PATH" | grep -q 'src/'; then bash .claude/scripts/smart-test.sh "$CLAUDE_FILE_PATH" 2>/dev/null || true; fi"
}
]
}
]
}
}
Hooks trong mảng chạy tuần tự — Prettier trước, ESLint sau, cuối cùng là test. Nếu bất kỳ hook nào fail (trả về exit code khác 0), các hook sau vẫn tiếp tục chạy trừ khi bạn cấu hình khác.
Cấu hình hooks thực tế cho các loại project
Project React/Next.js
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.ts|*.tsx|*.js|*.jsx|*.css) npx prettier --write "$CLAUDE_FILE_PATH" ;; esac"
},
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.ts|*.tsx) npx tsc --noEmit 2>&1 | head -20 ;; esac"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if echo "$CLAUDE_TOOL_INPUT" | grep -q 'git commit'; then npx tsc --noEmit && npx eslint src/ --max-warnings 0; fi"
}
]
}
]
}
}
Project Python/Django
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.py) black "$CLAUDE_FILE_PATH" && isort "$CLAUDE_FILE_PATH" && ruff check "$CLAUDE_FILE_PATH" --fix ;; esac"
}
]
}
]
}
}
Project Go
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "case "$CLAUDE_FILE_PATH" in *.go) gofmt -w "$CLAUDE_FILE_PATH" && go vet ./... 2>&1 | head -10 ;; esac"
}
]
}
]
}
}
Hooks nâng cao: Conditional logic và custom scripts
Khi cần logic phức tạp hơn một dòng lệnh, tốt nhất là viết script riêng và gọi từ hook.
Script hook thông minh: Chỉ chạy test cho file thay đổi
Tạo file .claude/scripts/post-write-hook.sh:
#!/bin/bash
# post-write-hook.sh — Hook thông minh chạy sau khi Claude ghi file
FILE="$CLAUDE_FILE_PATH"
PROJECT="$CLAUDE_PROJECT_DIR"
# 1. Auto-format dựa trên loại file
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE" 2>/dev/null
;;
*.py)
black "$FILE" 2>/dev/null
;;
*.go)
gofmt -w "$FILE" 2>/dev/null
;;
esac
# 2. Chạy test liên quan (nếu file nằm trong src/)
if echo "$FILE" | grep -q "src/"; then
# Tìm file test tương ứng
TEST_FILE=$(echo "$FILE" | sed 's|src/|test/|' | sed 's|\.ts$|.test.ts|')
if [ -f "$TEST_FILE" ]; then
echo "[Hook] Running tests for $TEST_FILE"
npx jest "$TEST_FILE" --bail --silent 2>&1 | tail -5
fi
fi
# 3. Log thay đổi
echo "[$(date '+%H:%M:%S')] Written: $FILE" >> "$PROJECT/.claude/hooks.log"
Cấu hình hook gọi script:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/scripts/post-write-hook.sh"
}
]
}
]
}
}
Hook bảo vệ file quan trọng
Ngăn Claude vô tình ghi đè lên các file cấu hình quan trọng:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "PROTECTED_FILES='.env .env.production docker-compose.yml package-lock.json yarn.lock'; for f in $PROTECTED_FILES; do if echo "$CLAUDE_FILE_PATH" | grep -q "$f"; then echo "BLOCKED: $CLAUDE_FILE_PATH is a protected file." && exit 1; fi; done"
}
]
}
]
}
}
Khi Claude cố ghi vào file được bảo vệ, hook sẽ trả về exit code 1 và thông báo lý do. Claude sẽ nhận được thông báo và thử cách tiếp cận khác hoặc hỏi bạn.
So sánh hooks với các phương pháp tự động hóa khác
Hooks không phải cách duy nhất để tự động hóa workflow trong Claude Code. Dưới đây là so sánh với các phương pháp khác:
- CLAUDE.md instructions: Viết trong CLAUDE.md để nhắc Claude chạy lệnh. Ưu điểm: linh hoạt, dễ thay đổi. Nhược điểm: Claude có thể quên, không đảm bảo 100%
- Git hooks: Pre-commit, pre-push hooks truyền thống. Ưu điểm: hoạt động ngoài Claude Code. Nhược điểm: chỉ trigger khi commit/push, không theo dõi file write
- Claude Code hooks: Trigger chính xác khi Claude dùng tool. Ưu điểm: đảm bảo 100%, đúng thời điểm. Nhược điểm: chỉ hoạt động trong Claude Code
- IDE extensions: Như format-on-save trong VS Code. Ưu điểm: quen thuộc. Nhược điểm: không áp dụng khi Claude Code chạy ngoài IDE
Phương pháp tối ưu là kết hợp: Claude Code hooks cho auto-format và test nhanh, git hooks cho validation cuối cùng trước commit, và CLAUDE.md cho các hướng dẫn mềm.
Hiệu suất và tối ưu hooks
Hooks chạy đồng bộ — Claude Code phải đợi hook hoàn thành trước khi tiếp tục. Điều này có nghĩa là hooks chạy chậm sẽ làm giảm tốc độ làm việc đáng kể. Dưới đây là các mẹo tối ưu:
- Giới hạn phạm vi file: Dùng điều kiện case/if để hook chỉ chạy trên file cần thiết. Không chạy ESLint cho file JSON, không chạy Prettier cho file Python
- Dùng --bail cho test: Flag --bail dừng test suite ngay khi có test đầu tiên fail, tiết kiệm thời gian chờ
- Chuyển hướng output: Dùng 2>/dev/null và | tail -N để giảm output không cần thiết. Claude Code đọc output của hook, output dài làm chậm xử lý
- Tránh install trong hook: Không chạy npm install hoặc pip install trong hook. Đảm bảo dependencies đã được install trước khi làm việc
- Dùng cache: Nhiều linter hỗ trợ cache (ESLint --cache, mypy --incremental). Kích hoạt cache giúp lần chạy thứ hai nhanh hơn nhiều
Đo thời gian chạy hook
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "START=$(date +%s%N); npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null; END=$(date +%s%N); echo "Hook took $(( (END-START)/1000000 ))ms" >> /tmp/claude-hooks-timing.log"
}
]
}
]
}
}
Kiểm tra log timing để xác định hook nào chạy chậm và cần tối ưu. Mục tiêu là mỗi hook hoàn thành trong dưới 2 giây — nếu lâu hơn, người dùng sẽ cảm thấy Claude Code phản hồi chậm.
Bước tiếp theo
Hooks là công cụ mạnh mẽ biến Claude Code thành một phần không thể tách rời trong quy trình phát triển. Bắt đầu đơn giản — thêm một PostToolUse hook cho auto-format — rồi mở rộng dần sang lint, test, và notification khi bạn đã quen với cơ chế hoạt động. Quan trọng nhất là hooks phải giúp bạn làm việc nhanh hơn chứ không chậm hơn — nếu hook chạy quá lâu hoặc hay gây lỗi giả, hãy điều chỉnh hoặc gỡ bỏ. Tìm hiểu thêm các tính năng nâng cao tại Thư viện Nâng cao Claude.
Bai viet co huu ich khong?
Bản quyền thuộc về tác giả. Vui lòng dẫn nguồn khi chia sẻ.





