Phân tích đầy đủ cơ chế Claude Code Hooks: năm loại sự kiện, kiểm soát mã thoát và bốn cấu hình thực tế để làm cho các hành động AI có thể kiểm toán, chặn và tự động hóa.
Mỗi lần Claude Code đọc file, viết code hoặc thực thi lệnh, một hệ thống sự kiện đang chạy trong nền. Hooks là giao diện để kết nối vào hệ thống đó — bạn có thể chèn logic của mình vào bất kỳ thời điểm nào: tự động chạy linter, ghi log, chặn các hành động nguy hiểm, hoặc kích hoạt bất kỳ lệnh shell nào.
Bài viết này trình bày đầy đủ cơ chế Hooks, cách cấu hình và sử dụng thực tế.
Hooks là các lệnh shell được cấu hình trong settings.json mà Claude Code tự động thực thi khi các sự kiện cụ thể xảy ra.
Phép so sánh trực tiếp nhất: git hooks. Git có thể kích hoạt script trước và sau các thao tác như commit, push. Claude Code Hooks hoạt động hoàn toàn tương tự — điểm khác biệt là các điểm kích hoạt là lời gọi công cụ của AI.
Tại sao điều này quan trọng?
Claude Code càng mạnh, bạn càng cần một lớp kiểm soát xác định. Hooks cung cấp:
- Đảm bảo thực thi không phụ thuộc vào prompt (Claude có thể bỏ qua hướng dẫn, nhưng hook luôn chạy)
- Bản ghi hoạt động có thể kiểm tra
- Kiểm tra chất lượng tự động
| Loại | Kích hoạt | Dùng phổ biến |
|---|---|---|
PreToolUse |
Trước khi gọi công cụ | Chặn thao tác nguy hiểm, ghi log ý định |
PostToolUse |
Sau khi gọi công cụ | Auto-lint, chạy test |
PreCompact |
Trước khi nén context | Lưu snapshot trạng thái hiện tại |
Notification |
Khi Claude gửi thông báo | Thông báo desktop, tin nhắn Slack |
Stop |
Khi Claude hoàn thành phản hồi | Tổng hợp log, kích hoạt luồng tiếp theo |
Được dùng nhiều nhất là PreToolUse và PostToolUse, để chặn và xử lý sau các lời gọi công cụ.
Hooks được viết trong ~/.claude/settings.json (toàn cục) hoặc .claude/settings.json ở thư mục gốc dự án (cấp dự án):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
Ba trường cốt lõi:
matcher: Regex khớp với tên công cụ, xác định hook này kích hoạt sau lời gọi công cụ nào. "Write|Edit" kích hoạt khi viết hoặc chỉnh sửa file. Để trống hoặc dùng ".*" để khớp tất cả công cụ.type: Hiện chỉ có "command".command: Bất kỳ lệnh shell nào.Tên công cụ cần biết khi viết matcher:
| Tên công cụ | Thao tác |
|---|---|
Write |
Viết file mới |
Edit |
Chỉnh sửa file |
Bash |
Thực thi lệnh shell |
Read |
Đọc file |
Glob |
Tìm kiếm file |
Grep |
Tìm kiếm nội dung |
TodoWrite |
Cập nhật danh sách việc cần làm |
Khi thực thi, dữ liệu được truyền dưới dạng JSON qua stdin:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUse nhận tool_input (đối số trước khi gọi). PostToolUse cũng nhận tool_response (giá trị trả về của công cụ).
Ví dụ hook với logic điều kiện:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# Chỉ chạy rubocop trên file .rb
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
Mã thoát của hook kiểm soát hành vi tiếp theo của Claude Code:
| Mã thoát | Ý nghĩa |
|---|---|
0 |
Thành công, tiếp tục thực thi |
2 |
Chặn: hủy lời gọi công cụ hiện tại, phản hồi stderr cho Claude |
| Khác (khác 0) | Ghi log lỗi, nhưng tiếp tục thực thi |
Mã thoát 2 hữu ích nhất — cho phép viết logic chặn trong PreToolUse để ngăn Claude thực hiện một thao tác và giải thích lý do.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"Không được phép xóa thư mục migrations hoặc seeds\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude đã hoàn thành\" with title \"Claude Code\"'"
}
]
}
]
}
}
Hooks có thể đặt ở hai nơi:
Toàn cục (~/.claude/settings.json): Quy tắc áp dụng cho tất cả dự án — log, thông báo.
Cấp dự án (.claude/settings.json): Kiểm tra đặc thù của dự án. Hooks cấp dự án và toàn cục được gộp lại và cùng chạy.
Với dự án nhóm, nên commit settings.json cấp dự án vào git để mọi người chạy cùng hooks.
Các bước xử lý sự cố khi hook không hoạt động:
write khác Writeecho "hook triggered" >> /tmp/hook.log để xác nhận kích hoạtGiá trị cốt lõi của Hooks là kết nối hành vi AI không thể đoán trước với các tiêu chuẩn kỹ thuật xác định. Claude có thể quên chạy test, nhưng PostToolUse hook thì không. Claude có thể vô tình xóa file, nhưng bộ chặn PreToolUse sẽ không cho phép.
Bắt đầu với một hook: auto-lint sau khi viết file. Chạy ổn rồi mới thêm cái khác.