Hook của Claude Code là các lệnh (hoặc HTTP call, prompt và một vài loại handler khác) mà Claude Code tự động chạy tại những thời điểm cố định trong một phiên, chẳng hạn trước khi một tool chạy, khi cần cấp quyền hoặc khi Claude trả lời xong. Bạn khai báo chúng dưới key hooks trong một file settings như ~/.claude/settings.json. Mỗi hook nhận JSON mô tả sự kiện qua stdin và có thể phản hồi bằng exit code hoặc một object JSON trên stdout, nhờ đó nó có thể ghi log, gửi thông báo, chặn một hành động hoặc trả lời thay bạn.
01Cấu trúc của một hook
Cấu hình hook gồm ba cấp:
- Hook event, ví dụ
PreToolUsehoặcStop. - Matcher group, dùng để lọc khi nào hook được kích hoạt, ví dụ "chỉ với tool Bash".
- Một hoặc nhiều hook handler, tức thứ thực sự được chạy.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}
Ví dụ này chạy một script lint sau khi Claude ghi hoặc sửa file. Để thêm event thứ hai, hãy thêm một key ngang hàng trong cùng object hooks.
02Hook nằm ở đâu
~/.claude/settings.jsonTất cả project của bạnKhông, chỉ trên máy bạn.claude/settings.jsonMột projectCó, có thể commit.claude/settings.local.jsonMột projectKhônghooks/hooks.json của pluginKhi plugin đang được bậtCóHook từ các file khác nhau được cộng dồn chứ không ghi đè nhau. Gõ /hooks trong Claude Code để xem mọi hook đang hoạt động và nguồn gốc của chúng.
Trong phiên tương tác, Claude Code sẽ tạm giữ các hook, kể cả hook của chính bạn, cho đến khi bạn chấp nhận hộp thoại workspace trust cho thư mục đó.
03Các loại handler
Phần lớn hook là "type": "command", tức chạy một lệnh shell. Ngoài ra còn bốn loại khác:
http: POST JSON của event tới một URL và đọc response body.mcp_tool: gọi một tool trên MCP server mà bạn đã cấu hình.prompt: hỏi model Claude một câu hỏi single-turn và đọc quyết định dạng JSON.agent: khởi chạy một subagent có thể đọc file trước khi quyết định (thử nghiệm).
Command hook chạy với toàn bộ quyền của user, thông qua sh -c trên macOS và Linux, và Git Bash (hoặc PowerShell) trên Windows. Thêm "async": true để chạy hook ở chế độ nền; khi đó nó không thể đưa ra quyết định nữa.
04Matcher
matcher quyết định những lần xảy ra nào của event sẽ chạy hook. Giá trị được so khớp tùy theo event: tên tool với các tool event, loại thông báo với Notification, cách phiên bắt đầu với SessionStart, v.v.
"*",""hoặc không có matcher đều khớp với mọi thứ.- Chữ cái, chữ số,
_,-, dấu cách,|và,được hiểu là tên chính xác.Edit|Writekhớp với một trong hai tool. - Mọi giá trị khác được coi là regular expression của JavaScript, nên
mcp__memory__.*khớp với mọi tool của một MCP server.
Matcher phân biệt hoa thường. Một số event, bao gồm Stop và UserPromptSubmit, bỏ qua matcher. Với các tool event, trường if chứa một permission rule như "Bash(git *)" giúp thu hẹp phạm vi của từng handler hơn nữa.
05Input: hook của bạn nhận được gì
Mọi hook đều nhận JSON qua stdin với một số trường chung, gồm session_id, transcript_path, cwd và hook_event_name, cùng các trường riêng của từng event. Ví dụ, hook PreToolUse cho một lệnh Bash sẽ nhận thêm tool_name và tool_input.command. Đọc dữ liệu bằng jq hoặc bất kỳ JSON parser nào:
#!/bin/bash
input=$(cat)
cmd=$(jq -r '.tool_input.command // empty' <<<"$input")
echo "Claude wants to run: $cmd" >> ~/claude-commands.log
exit 0
06Output: exit code và JSON
Hook phản hồi lại theo một trong hai cách.
Exit code. 0 là thành công. 2 là lỗi chặn: với các event có thể chặn, hành động sẽ dừng lại và nội dung stderr của bạn trở thành lý do. Mọi code khác, kể cả 1 quen thuộc, đều là lỗi không chặn, nên hành động vẫn tiếp tục và bạn chỉ thấy thông báo lỗi hook. Nếu hook dùng để áp đặt một quy tắc, hãy exit 2, đừng dùng 1.
JSON trên stdout. Exit 0 và in ra một object JSON duy nhất để kiểm soát chi tiết hơn. Một số field dùng được ở mọi nơi, ví dụ continue: false để dừng hẳn Claude. Số khác thì tùy event: Stop dùng decision: "block" kèm reason, còn PreToolUse và PermissionRequest đặt quyết định bên trong hookSpecificOutput. Đừng in gì khác ra stdout: chỉ một dòng thừa từ shell profile cũng khiến Claude Code không đọc được JSON.
07Tất cả hook event
SessionStartMột session bắt đầu hoặc được tiếp tụcKhông (thêm context)SetupKhởi chạy với --init-only, hoặc --init / --maintenance cùng -pKhôngInstructionsLoadedMột file CLAUDE.md hoặc .claude/rules/*.md được loadKhôngUserPromptSubmitBạn gửi prompt, trước khi Claude nhận đượcCóUserPromptExpansionMột lệnh được gõ được mở rộng thành promptCóPreToolUseTrước mọi tool callCóPermissionRequestMột tool call cần quyết định cấp quyềnCho phép hoặc từ chối qua JSONPermissionDeniedAuto mode từ chối một tool callKhôngPostToolUseSau khi tool call thành côngKhông (tool đã chạy rồi)PostToolUseFailureSau khi tool call thất bạiKhôngPostToolBatchSau một loạt tool call song song, trước lần gọi model tiếp theoCóNotificationClaude Code gửi thông báoKhôngMessageDisplayTrong lúc hiển thị văn bản trả lời của ClaudeKhông (có thể thay đổi nội dung hiển thị)SubagentStart / SubagentStopMột subagent bắt đầu hoặc kết thúcChỉ SubagentStopTaskCreated / TaskCompletedMột task được tạo hoặc đánh dấu hoàn thànhCóStopClaude trả lời xongCó (giữ Claude tiếp tục chạy)StopFailureLượt kết thúc với lỗi APIKhôngTeammateIdleMột thành viên trong agent team sắp chuyển sang idleCóConfigChangeMột file settings thay đổi trong sessionCóCwdChangedThư mục làm việc thay đổiKhôngDirectoryAddedMột thư mục được thêm bằng /add-dirKhôngFileChangedMột file đang được theo dõi thay đổi trên ổ đĩaKhôngWorktreeCreate / WorktreeRemoveMột worktree được tạo hoặc xóaCóPreCompact / PostCompactTrước và sau khi nén contextChỉ PreCompactPreModelSwitch / PostModelSwitchTrước và sau khi đổi modelChỉ PreModelSwitchElicitation / ElicitationResultKhi một MCP server yêu cầu nhập liệu và sau khi bạn trả lờiCóSessionEndKhi một phiên kết thúcKhôngAnthropic thỉnh thoảng bổ sung event mới, nên hãy xem tài liệu tham khảo về hook để biết danh sách hiện tại và các trường input của từng event.
08Những event quan trọng cho thông báo
Bốn event sau gần như bao quát mọi thứ bạn muốn được báo:
Stopcho "Claude đã xong". Xem hướng dẫn về Stop hook.Notificationcho "Claude đang chờ bạn", với các loại nhưpermission_promptvàidle_prompt. Xem hướng dẫn về Notification hook.PermissionRequestđể được báo, hoặc trả lời, ngay khi cần cấp quyền. Xem hướng dẫn về PermissionRequest.PreToolUsetrên toolAskUserQuestion, để xem hoặc trả lời các câu hỏi trắc nghiệm của Claude. Xem trả lời câu hỏi từ điện thoại.
Nếu muốn có sẵn cấu hình desktop để dán vào, hãy bắt đầu với cách nhận thông báo khi Claude Code chạy xong.
09Claude Code Notifier nằm ở đâu trong bức tranh này
Claude Code Notifier được xây dựng hoàn toàn dựa trên các hook này. Công cụ dòng lệnh miễn phí của nó, ccn, chạy trên Mac, Windows và Linux. Khi cài đặt, nó thêm bảy hook vào ~/.claude/settings.json, mỗi hook chạy ccn hook, và tạo một bản sao lưu file một lần. ccn chỉ chạy vài mili giây cho mỗi event rồi thoát, nên không có gì chạy nền cả.
Khi dùng riêng, nó hiển thị thông báo trên desktop. Khi ghép nối với app iPhone hoặc Android, chính các hook đó sẽ gửi câu trả lời của bạn ngược lại: cho phép hoặc từ chối quyền, trả lời câu hỏi, hoặc gửi chỉ dẫn tiếp theo sau khi Claude dừng. Đây là công cụ độc lập, không phải do Anthropic phát triển. Nếu bạn chỉ cần ghi log hoặc chạy lint, tự viết vài dòng sẽ đơn giản hơn. Chi tiết có trên trang ccn.
10Câu hỏi thường gặp
Làm sao test hook mà không phải chờ Claude?
Pipe JSON mẫu vào script và kiểm tra exit code: echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $?. Sau đó chạy /hooks trong Claude Code để xác nhận hook đã được đăng ký đúng event.
Tại sao hook của tôi không chạy?
Nguyên nhân thường gặp là JSON không hợp lệ trong settings.json (dấu phẩy thừa ở cuối, comment), matcher không khớp chính xác (có phân biệt hoa thường), script chưa có quyền thực thi, hoặc thư mục bạn chưa trust. Khởi động Claude Code bằng claude --debug và đọc debug log để xem cái gì đã chạy. Danh sách đầy đủ có trong checklist khắc phục sự cố.
Làm sao tạm tắt toàn bộ hook?
Đặt "disableAllHooks": true trong một file settings. Để bỏ qua hook chỉ cho một lần chạy, khởi động Claude Code với --settings '{"disableAllHooks": true}'. Không có công tắc tắt riêng từng hook; bạn phải xóa entry của nó. Cả hai cách đều không tắt được các hook do tổ chức của bạn đặt trong managed settings.
Timeout mặc định của hook là bao lâu?
Mười phút (600 giây) cho hook dạng command, HTTP và MCP tool, 30 giây cho prompt hook và 60 giây cho agent hook. Một số event dùng mặc định ngắn hơn, và bạn có thể đặt timeout (tính bằng giây) cho bất kỳ handler nào.