Claude Code Hooks: tự động format, chặn lệnh nguy hiểm, ép chạy test — không cần nhắc
Tóm tắt
Hook là lệnh shell (hoặc HTTP, prompt…) chạy tại các sự kiện như PreToolUse, PostToolUse, Stop. Exit code 2 ở PreToolUse chặn tool call và gửi lý do cho Claude; ở Stop thì buộc Claude làm tiếp. Cấu hình trong .claude/settings.json, xem bằng /hooks.
Bạn có thể dặn Claude Code trong CLAUDE.md “luôn format code sau khi sửa”, “đừng bao giờ chạy rm -rf”, “chạy test trước khi báo xong”. Model sẽ làm theo… phần lớn thời gian. Hooks biến những lời dặn đó thành quy tắc chắc chắn: một lệnh shell (hoặc HTTP, prompt, agent) tự chạy tại đúng thời điểm trong vòng đời của Claude Code, có thể chặn hành động hoặc bắt Claude làm tiếp.
Hook hoạt động thế nào?
Một hook gồm ba phần:
- Sự kiện (event): thời điểm hook chạy, ví dụ
PreToolUse(trước khi dùng tool),Stop(khi Claude định dừng). - Matcher: bộ lọc, ví dụ chỉ khi tool là
Bash, hoặcEdit|Write. - Handler: việc cần làm — chạy lệnh shell, gọi HTTP endpoint, gọi tool MCP, hỏi một model (prompt), hoặc sinh subagent kiểm tra.
Claude Code gửi dữ liệu sự kiện dạng JSON vào stdin của hook (gồm session_id, cwd, tool_name, tool_input…). Hook trả lời bằng exit code hoặc JSON trên stdout.
Các sự kiện hay dùng
| Sự kiện | Chạy khi | Chặn được? |
|---|---|---|
SessionStart | Bắt đầu/tiếp tục phiên | Không — dùng để nạp thêm context |
UserPromptSubmit | Bạn gửi prompt | Có |
PreToolUse | Trước khi Claude dùng tool | Có — cho phép, từ chối, hỏi, hoặc sửa input |
PostToolUse | Sau khi tool chạy xong | Không (tool đã chạy) — nhưng gửi phản hồi được |
Notification | Claude Code gửi thông báo (vd chờ bạn duyệt) | Không |
Stop / SubagentStop | Claude / subagent định kết thúc lượt | Có — bắt làm tiếp |
PreCompact | Trước khi nén hội thoại | Có |
TaskCompleted, TeammateIdle | Task / teammate trong agent team | Có |
SessionEnd | Kết thúc phiên | Không |
Ngoài ra còn nhiều sự kiện khác như ConfigChange, FileChanged, PermissionRequest, PreModelSwitch… — gõ /hooks trong Claude Code để xem hook đang cấu hình và nguồn của chúng.
Exit code — phần quan trọng nhất
| Exit code | Ý nghĩa |
|---|---|
| 0 | Thành công. Nếu stdout là một object JSON, các trường của nó được áp dụng. |
| 2 | Lỗi chặn (với sự kiện chặn được). Nội dung stderr được gửi cho Claude làm lý do. |
| Khác (vd 1) | Lỗi không chặn: hành động vẫn diễn ra, bạn chỉ thấy thông báo “hook error”. |
Lỗi phổ biến nhất của người mới: dùng exit 1 để chặn — không chặn được. Luật bắt buộc phải dùng exit 2. Với Stop, exit 2 nghĩa là “chưa được dừng, làm tiếp”.
Cấu hình ở đâu?
| Vị trí | Phạm vi |
|---|---|
~/.claude/settings.json | Mọi dự án trên máy bạn |
.claude/settings.json | Dự án — commit vào repo để cả team dùng |
.claude/settings.local.json | Dự án, chỉ máy bạn (gitignore) |
| Managed settings | Toàn tổ chức |
| Plugin, frontmatter của skill/subagent | Khi plugin bật / khi skill hoặc subagent chạy |
Ví dụ 1: Tự format sau mỗi lần sửa file
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}]
}]
}
}
Hook đọc JSON từ stdin, lấy đường dẫn file vừa sửa và chạy Prettier. Claude không cần nhớ format nữa.
Ví dụ 2: Chặn lệnh rm
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}]
}]
}
}
#!/bin/bash
# .claude/hooks/block-rm.sh (nhớ chmod +x)
echo "Không được xoá file bằng rm. Hãy hỏi người dùng trước." >&2
exit 2
Trường if dùng cú pháp của permission rule (Bash(git *), Edit(*.ts)) để lọc hẹp hơn matcher. Lưu ý: if chỉ là “best-effort”; muốn chặn tuyệt đối, dùng permission deny thay vì chỉ dựa vào hook.
Ví dụ 3: Không cho Claude dừng khi test còn đỏ
#!/bin/bash
# Hook cho sự kiện Stop
if ! npm test --silent > /tmp/test.log 2>&1; then
echo "Test vẫn còn lỗi. Đọc /tmp/test.log và sửa trước khi kết thúc." >&2
exit 2
fi
exit 0
Đây là cách biến “Definition of Done” thành quy tắc máy kiểm tra được.
Ví dụ 4: Từ chối có lý do bằng JSON
Thay vì exit 2, hook PreToolUse có thể in JSON để quyết định chi tiết hơn:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Không được ghi trực tiếp vào database production"
}
}
permissionDecision nhận allow, deny, ask hoặc defer; có thể kèm updatedInput để sửa tham số trước khi chạy. JSON phải là nội dung duy nhất trên stdout — một dòng chào từ shell profile cũng đủ làm hỏng việc parse.
Các loại handler khác
http: POST JSON sự kiện tới một URL — hợp để ghi log tập trung hoặc gửi cảnh báo.mcp_tool: gọi một tool của MCP server đã cấu hình.prompt: nhờ một model đánh giá nhanh một lượt (mặc định timeout 30 giây).agent: sinh subagent có Read/Grep/Glob để kiểm tra điều kiện (thử nghiệm).
Timeout mặc định của hook lệnh là 600 giây. Biến $CLAUDE_PROJECT_DIR trỏ tới thư mục gốc dự án — dùng nó để gọi script theo đường dẫn tuyệt đối.
Bảo mật và kiểm soát
- Hook chạy lệnh tuỳ ý với quyền của bạn — đọc kỹ hook đi kèm plugin, skill, subagent trước khi bật.
- Hook trong dự án tuân theo cơ chế tin cậy thư mục.
- Tổ chức có thể dùng
allowManagedHooksOnlyđể chỉ cho phép hook do quản trị cấp. - Tắt toàn bộ hook tạm thời:
"disableAllHooks": true. - Gỡ lỗi:
claude --debughoặc/debugghi đầy đủ stderr và output của hook.
Áp dụng thế nào?
- Dev: Bắt đầu với hai hook rẻ mà lợi: format sau khi sửa file và chặn
rm/git push --force. - Tech Lead: Đưa hook vào
.claude/settings.jsoncủa repo, review như review CI config. HookStopchạy test + lint là cách rẻ nhất để giảm PR “AI làm xong nhưng build đỏ”. - PM: Mỗi quy tắc “team hay quên” (cập nhật changelog, không sửa file migration cũ…) là ứng viên cho hook. Biến checklist thành tự động thay vì nhắc trong mỗi buổi review.
Nguồn & tham khảo
- Hooks reference — Claude Code Docs