Hooks
Hooks là lệnh shell do bạn định nghĩa, chạy tự động tại các thời điểm cố định trong vòng đời của Claude Code - cho bạn kiểm soát tất định (deterministic): một số hành động luôn xảy ra đúng như cấu hình, thay vì phụ thuộc vào việc model có “nhớ” hay quyết định làm theo hay không. Đây là điểm khác biệt so với việc chỉ dặn dò trong CLAUDE.md - xem thêm ở Khi nào dùng Hooks thay vì CLAUDE.md.
Với quyết định cần phán đoán thay vì rule tất định, có thể dùng hook dạng prompt hoặc hook dạng agent, dùng chính một model Claude để đánh giá điều kiện.
Thiết lập hook đầu tiên
Phần tiêu đề “Thiết lập hook đầu tiên”Thêm block hooks vào một settings file. Ví dụ dưới tạo desktop notification mỗi khi Claude đang chờ bạn phản hồi, để bạn không phải nhìn chằm chằm vào terminal.
Thêm vào ~/.claude/settings.json (tạo file nếu chưa có):
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" } ] } ] }}Trên Linux dùng notify-send 'Claude Code' 'Claude Code needs your attention'; trên Windows PowerShell dùng [System.Windows.Forms.MessageBox]::Show(...). Nếu settings file đã có key hooks, thêm Notification như một sibling của các event key khác - mỗi tên event là một key riêng trong cùng object hooks, không ghi đè các event đã có.
Chạy /hooks để mở trình duyệt hook - liệt kê mọi event khả dụng, kèm số hook đang cấu hình cho mỗi event. Chọn Notification để xác nhận hook mới xuất hiện. Menu này chỉ để xem; muốn thêm/sửa/xoá, sửa trực tiếp settings JSON hoặc nhờ Claude sửa giúp.
Nhấn Shift+Tab tới khi status bar hiện ⏸ manual mode on, nhờ Claude làm việc gì cần permission, rồi chuyển sang cửa sổ khác - bạn sẽ nhận được desktop notification.
Các sự kiện (event)
Phần tiêu đề “Các sự kiện (event)”Claude Code kích hoạt hook event tại các thời điểm cố định trong vòng đời. Khi một event fire, Claude Code chạy song song mọi hook khớp với nó. Danh sách đầy đủ (Claude Code có khoảng 30 event):
| Event | Thời điểm kích hoạt |
|---|---|
SessionStart |
Khi một phiên bắt đầu hoặc resume |
Setup |
Khi bạn chạy Claude Code với --init-only, hoặc --init/--maintenance ở mode -p - dùng cho chuẩn bị một lần trong CI/script |
UserPromptSubmit |
Khi bạn gửi prompt, trước khi Claude xử lý |
UserPromptExpansion |
Khi một lệnh bạn gõ mở rộng thành prompt, trước khi tới Claude - có thể chặn việc mở rộng |
PreToolUse |
Trước khi một tool call thực thi - có thể chặn |
PermissionRequest |
Khi một tool call cần quyết định permission |
PermissionDenied |
Khi auto mode từ chối một tool call, kể cả khi classifier không đưa ra verdict |
PostToolUse |
Sau khi tool call thành công |
PostToolUseFailure |
Sau khi tool call thất bại |
PostToolBatch |
Sau khi một batch tool call song song hoàn tất, trước lần gọi model kế tiếp |
Notification |
Khi Claude Code gửi thông báo |
MessageDisplay |
Trong lúc text tin nhắn assistant đang hiển thị |
SubagentStart / SubagentStop |
Khi subagent bắt đầu / hoàn thành |
TaskCreated / TaskCompleted |
Khi task được tạo qua TaskCreate / đánh dấu hoàn thành |
Stop |
Khi Claude kết thúc lượt trả lời |
StopFailure |
Khi lượt kết thúc do lỗi API |
TeammateIdle |
Khi một teammate trong agent team sắp chuyển sang idle |
InstructionsLoaded |
Khi một file CLAUDE.md hoặc .claude/rules/*.md được nạp vào ngữ cảnh |
ConfigChange |
Khi một file cấu hình đổi trong lúc phiên đang chạy |
CwdChanged |
Khi working directory đổi, ví dụ Claude chạy lệnh cd |
DirectoryAdded |
Khi một working directory được thêm giữa phiên qua /add-dir |
FileChanged |
Khi một file đang theo dõi đổi trên đĩa. matcher chỉ định tên file cần theo dõi |
WorktreeCreate / WorktreeRemove |
Khi một worktree được tạo/gỡ |
PreCompact / PostCompact |
Trước / sau khi context compaction |
Elicitation / ElicitationResult |
Khi MCP server yêu cầu input người dùng / sau khi người dùng phản hồi |
SessionEnd |
Khi một phiên kết thúc |
Mỗi hook có một type quyết định cách nó chạy. Đa số dùng "type": "command" (chạy shell command). Bốn loại khác: "http" (POST dữ liệu event tới một URL, xem HTTP hooks), "mcp_tool" (gọi tool trên MCP server đã kết nối), "prompt" (đánh giá bằng LLM một lượt, xem hook dạng prompt), và "agent" (xác minh nhiều lượt có dùng tool, xem hook dạng agent, đang thử nghiệm).
Ví dụ 1: tự động format sau khi sửa file
Phần tiêu đề “Ví dụ 1: tự động format sau khi sửa file”Chạy Prettier trên mọi file Claude vừa sửa. Thêm vào .claude/settings.json ở gốc dự án:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" } ] } ] }}matcher: "Edit|Write" nghĩa là hook này chỉ chạy khi Claude dùng tool sửa/tạo file, không chạy với Bash, Read… Lệnh dùng jq để lấy đường dẫn file từ dữ liệu JSON Claude Code truyền vào qua stdin. Khi hook thành công, Claude Code không hiện gì trong hội thoại - kiểm tra file đã format lại, hoặc xem kỹ thuật debug bên dưới.
Ví dụ 2: chặn sửa file nhạy cảm
Phần tiêu đề “Ví dụ 2: chặn sửa file nhạy cảm”Ngăn Claude sửa các file như .env, package-lock.json, hay bất cứ thứ gì trong .git/. Tạo script .claude/hooks/protect-files.sh:
#!/bin/bashINPUT=$(cat)FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED[@]}"; do if [[ "$FILE_PATH" == *"$pattern"* ]]; then echo "Blocked: $FILE_PATH khớp mẫu bị chặn '$pattern'" >&2 exit 2 fidone
exit 0Cấp quyền thực thi: chmod +x .claude/hooks/protect-files.sh. Đăng ký hook trong .claude/settings.json:
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" } ] } ] }}Ví dụ 3: tự động phê duyệt một permission prompt cụ thể
Phần tiêu đề “Ví dụ 3: tự động phê duyệt một permission prompt cụ thể”Bỏ qua dialog phê duyệt cho tool call bạn luôn cho phép. Ví dụ dưới tự phê duyệt ExitPlanMode (tool Claude gọi khi trình bày xong plan và xin tiếp tục), nên bạn không bị hỏi mỗi lần plan sẵn sàng:
{ "hooks": { "PermissionRequest": [ { "matcher": "ExitPlanMode", "hooks": [ { "type": "command", "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'" } ] } ] }}Khác với ví dụ 1-2 dùng exit code, tự động phê duyệt cần hook ghi một JSON decision ra stdout. Claude Code chạy hook PermissionRequest ngay trước khi nó định hỏi bạn; nếu hook trả về "behavior": "allow", Claude Code tự trả lời thay bạn. Giữ matcher càng hẹp càng tốt - matcher .* hoặc để trống sẽ tự phê duyệt mọi permission prompt, kể cả ghi file và lệnh shell.
Cách hook giao tiếp với Claude Code
Phần tiêu đề “Cách hook giao tiếp với Claude Code”Hook nhận dữ liệu sự kiện dạng JSON qua stdin. Mọi event đều có field chung như session_id, cwd; mỗi loại event thêm field riêng - ví dụ PreToolUse cho lệnh Bash có tool_name, tool_input.command. Hook báo lại quyết định qua exit code hoặc JSON có cấu trúc in ra stdout.
Exit code:
- Exit 0 - không phản đối; với
PreToolUsevẫn phải qua luồng xin phép thông thường. VớiUserPromptSubmit,UserPromptExpansion,SessionStart, stdout được thêm vào ngữ cảnh của Claude dưới dạng plain text. - Exit 2 - chặn hành động. Ghi lý do ra
stderr; tuỳ event, lý do đó được đưa cho Claude làm feedback, hiển thị cho người dùng, hoặc (vớiConfigChange,Elicitation) không hiện gì. Một số event nhưSessionStart,Setupkhông chặn được - exit 2 chỉ hiện stderr cho người dùng rồi tiếp tục. - Mã khác - tuỳ nội dung stdout: một JSON object hợp lệ theo schema thì Claude Code bỏ qua exit code và dùng JSON đó; JSON không hợp lệ schema là lỗi không chặn; stdout không phải JSON hoặc rỗng cũng là lỗi không chặn, transcript hiện
<hook name> hook error.
JSON có cấu trúc: exit 0 kèm in một JSON object ra stdout cho kiểm soát chi tiết hơn chỉ block/im lặng. Ví dụ PreToolUse từ chối một tool call và báo lý do:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Use rg instead of grep for better performance" }}Các giá trị permissionDecision cho PreToolUse: "allow" (bỏ qua permission prompt tương tác - deny/ask rule tường minh vẫn áp dụng), "deny" (huỷ tool call, gửi lý do cho Claude), "ask" (hiện permission prompt bình thường). Một giá trị thứ tư, "defer", chỉ dùng trong non-interactive mode (-p) để giữ tool call lại cho một Agent SDK wrapper xử lý sau.
Event khác dùng pattern quyết định khác - ví dụ PostToolUse/Stop dùng field decision: "block" cấp cao nhất, PermissionRequest dùng hookSpecificOutput.decision.behavior. Với UserPromptSubmit, dùng hookSpecificOutput.additionalContext để bơm text vào ngữ cảnh Claude - phải lồng trong hookSpecificOutput, đặt ở cấp cao nhất sẽ bị Claude Code bỏ qua.
Kết hợp kết quả nhiều hook
Phần tiêu đề “Kết hợp kết quả nhiều hook”Khi nhiều hook cùng khớp một event, mọi hook chạy xong hoàn toàn trước khi Claude Code gộp kết quả - một hook trả deny không dừng các hook khác đang chạy. Với quyết định permission ở PreToolUse, câu trả lời khắt khe nhất thắng, theo thứ tự deny, defer, ask, allow. Text từ additionalContext được giữ lại từ mọi hook và gửi cùng nhau cho Claude.
Lọc hook bằng matcher
Phần tiêu đề “Lọc hook bằng matcher”Không có matcher, hook fire ở mọi lần xảy ra event đó. Mỗi loại event khớp theo một field riêng:
| Event | Matcher lọc theo | Ví dụ giá trị |
|---|---|---|
PreToolUse, PostToolUse, PermissionRequest, PermissionDenied |
Tên tool | Bash, Edit|Write, mcp__.* |
SessionStart |
Cách phiên bắt đầu | startup, resume, clear, compact, fork |
Notification |
Loại thông báo | permission_prompt, idle_prompt, agent_needs_input… |
SubagentStart/SubagentStop |
Loại agent | general-purpose, Explore, Plan, tên tự đặt |
PreCompact/PostCompact |
Nguyên nhân compaction | manual, auto |
ConfigChange |
Nguồn cấu hình | user_settings, project_settings, local_settings, policy_settings, skills |
FileChanged |
Tên file theo dõi (literal, không phải regex) | .envrc|.env |
UserPromptSubmit, PostToolBatch, Stop, CwdChanged, MessageDisplay… |
Không hỗ trợ matcher | luôn fire |
Từ v2.1.191, dấu phẩy cũng phân tách các lựa chọn giống |, nên "Edit, Write" tương đương "Edit|Write".
Tool MCP theo quy ước đặt tên khác built-in tool: mcp__<server>__<tool>, ví dụ mcp__github__search_repositories. Dùng regex matcher để nhắm một server cụ thể (mcp__github__.*) hoặc nhiều server (mcp__.*__write.*).
Lọc theo tool và argument bằng field if
Phần tiêu đề “Lọc theo tool và argument bằng field if”Field if dùng cú pháp permission rule để lọc hook theo cả tên tool lẫn argument - sâu hơn matcher, vốn chỉ lọc theo tên tool ở cấp nhóm:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(git *)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh" } ] } ] }}Bộ lọc kiểm tra cả subcommand trong lệnh ghép (&&) và trong $()/backtick. Nó fail-open - chạy hook bất kể pattern nếu không parse được lệnh Bash - nên dùng permission system để ép buộc allow/deny cứng, không dùng if. Field này chỉ hoạt động với event liên quan tool: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied.
Vị trí đặt hook
Phần tiêu đề “Vị trí đặt hook”Vị trí đặt hook quyết định phạm vi áp dụng:
| Vị trí | Phạm vi | Chia sẻ được? |
|---|---|---|
~/.claude/settings.json |
Mọi dự án của bạn | Không, cục bộ máy bạn |
.claude/settings.json |
Một dự án | Có, commit vào repo |
.claude/settings.local.json |
Một dự án | Không, gitignore |
| Managed policy settings | Toàn tổ chức | Có, admin kiểm soát |
hooks/hooks.json của plugin |
Khi plugin được bật | Có, đóng gói cùng plugin |
| Frontmatter skill | Phần còn lại của phiên sau khi skill được gọi | Có, định nghĩa trong file skill |
| Frontmatter subagent | Trong lúc subagent đó chạy | Có, định nghĩa trong file subagent |
Đặt "disableAllHooks": true trong settings để tắt tạm toàn bộ hooks - hook ở cấp managed settings vẫn chạy trừ khi bật cờ này ở đó luôn. Nếu bạn sửa settings file trực tiếp trong lúc Claude Code đang chạy, file watcher thường tự nhận diện thay đổi hook.
Hook dạng prompt
Phần tiêu đề “Hook dạng prompt”Với quyết định cần phán đoán thay vì rule cố định, dùng hook type: "prompt". Thay vì chạy shell command, Claude Code gửi prompt của bạn cùng dữ liệu input của hook tới một model Claude (mặc định Haiku, đổi được bằng field model) để ra quyết định. Model chỉ trả về JSON: "ok": true (tiếp tục) hoặc "ok": false (tuỳ event: với Stop/SubagentStop, reason được đưa lại cho Claude để tiếp tục làm; với PreToolUse/PostToolUse, tool call bị chặn/lượt kết thúc trừ khi đặt continueOnBlock: true).
{ "hooks": { "Stop": [ { "hooks": [ { "type": "prompt", "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}." } ] } ] }}Hook dạng agent
Phần tiêu đề “Hook dạng agent”Khi việc xác minh cần đọc file hoặc chạy lệnh, dùng hook type: "agent". Khác hook dạng prompt (một lượt gọi LLM), hook dạng agent triệu tập một subagent có thể đọc file, tìm code, và dùng tool khác để xác minh điều kiện trước khi trả quyết định - timeout mặc định 60 giây, tối đa 50 lượt dùng tool:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "agent", "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS", "timeout": 120 } ] } ] }}$ARGUMENTS được Claude Code thay bằng JSON input của hook. Dùng hook dạng prompt khi dữ liệu input đã đủ để quyết định; dùng hook dạng agent khi cần xác minh so với trạng thái thật của codebase.
HTTP hooks
Phần tiêu đề “HTTP hooks”Dùng type: "http" để POST dữ liệu event tới một endpoint HTTP thay vì chạy shell command - hữu ích khi muốn một web server, cloud function, hay service dùng chung xử lý logic hook, ví dụ một audit service log tool-use event cho cả team:
{ "hooks": { "PostToolUse": [ { "hooks": [ { "type": "http", "url": "http://localhost:8080/hooks/tool-use", "headers": { "Authorization": "Bearer $MY_TOKEN" }, "allowedEnvVars": ["MY_TOKEN"] } ] } ] }}Endpoint trả về response body dùng cùng format JSON output như command hook - để chặn, trả 2xx kèm field hookSpecificOutput phù hợp (mã HTTP status không tự chặn được). Giá trị header hỗ trợ nội suy biến môi trường bằng $VAR_NAME; chỉ biến liệt kê trong allowedEnvVars được resolve.
Giới hạn và tương tác với permission mode
Phần tiêu đề “Giới hạn và tương tác với permission mode”- Command hook giao tiếp qua stdout/stderr/exit code, không gọi được lệnh
/hay tool call trực tiếp. - Timeout khác nhau theo loại:
command/http/mcp_tool10 phút (UserPromptSubmitrút xuống 30 giây,MessageDisplayxuống 10 giây);prompt30 giây;agent60 giây. Override bằng fieldtimeout(giây) trên từng hook. PostToolUsekhông “hoàn tác” được vì tool đã chạy xong.Stophook fire mỗi khi Claude kết thúc lượt trả lời, không chỉ lúc hoàn thành task, và không fire khi bạn tự ngắt (interrupt).- Khi nhiều hook
PreToolUsecùng trảupdatedInputđể sửa argument của tool, hook chạy xong sau cùng thắng - vì hook chạy song song nên thứ tự không tất định; tránh để nhiều hook cùng sửa input của một tool.
Hook PreToolUse fire trước mọi kiểm tra permission mode, ở mọi mode kể cả dontAsk. Một hook trả permissionDecision: "deny" chặn tool call ngay cả ở bypassPermissions hoặc --dangerously-skip-permissions - cho phép bạn ép buộc policy mà user không lách qua permission mode được. Chiều ngược lại không đúng: hook trả "allow" không bỏ qua được deny rule từ settings, và không tắt được prompt cho connector tool tổ chức đặt ask hay MCP tool đánh dấu requiresUserInteraction. Hook chỉ siết chặt thêm, không nới lỏng vượt quá permission rule cho phép.
Kỹ thuật debug
Phần tiêu đề “Kỹ thuật debug”Nhấn Ctrl+O để mở transcript view và xem kết quả hook. Chạy thành công thường không hiện gì trừ khi JSON trả về có systemMessage hoặc feedback của Stop hook. Lỗi chặn hiện feedback (lý do từ decision, hoặc stderr). Lỗi không chặn hiện <hook name> hook error kèm giải thích ngắn.
Để xem chi tiết đầy đủ (hook nào khớp, exit code, stdout, stderr), khởi động Claude Code với claude --debug-file /tmp/claude.log rồi tail -f /tmp/claude.log, hoặc chạy /debug giữa phiên nếu chưa bật cờ đó lúc khởi động.
Nếu Stop hook cứ chặn liên tục thay vì để Claude dừng, kiểm tra field stop_hook_active trong JSON input - Claude Code tự override sau 8 lần chặn liên tiếp không tiến triển; script của bạn nên tự exit sớm khi field này là true để tránh vòng lặp.
Khi nào dùng Hooks thay vì CLAUDE.md
Phần tiêu đề “Khi nào dùng Hooks thay vì CLAUDE.md”- CLAUDE.md: chỉ dẫn bằng ngôn ngữ tự nhiên, Claude cố gắng làm theo nhưng không đảm bảo 100% - phù hợp cho quy ước, ngữ cảnh, sở thích chung.
- Hooks: thực thi bắt buộc bằng shell script, luôn chạy đúng như cấu hình, không phụ thuộc vào việc model có tuân thủ hay không - phù hợp cho yêu cầu bắt buộc phải xảy ra (chạy lint trước commit, chặn thao tác nguy hiểm, ghi log audit…).
Dùng hooks khi hậu quả của việc Claude “quên” là không chấp nhận được; dùng CLAUDE.md cho phần còn lại.