30 sự kiện hook kích hoạt trong một phiên Claude Code. Chỉ 3 sự kiện trả lời được.
Danh sách đầy đủ các sự kiện hook của Claude Code, thời điểm từng sự kiện kích hoạt, 15 sự kiện nào có thể chặn, và quy tắc stdout âm thầm nuốt chửng đầu ra của phần lớn hook. Một tài liệu tham chiếu thực chiến, đúc kết từ việc chạy hook trong môi trường sản xuất qua hàng nghìn phiên agent.
Hai kiểu hỏng hóc cứ lặp đi lặp lại khi người ta đấu hook vào Claude Code, và chúng chẳng giống nhau chút nào.
Kiểu thứ nhất: bạn thêm một hook, không có gì xảy ra. Không lỗi, không cảnh báo, không một dòng log nào. Hook đơn giản là không bao giờ chạy.
Kiểu thứ hai: hook rõ ràng có chạy, bạn thấy được tác động của nó trên đĩa, nhưng thông điệp nó in ra cho agent thì không bao giờ đến nơi. Agent hành xử như thể hook chưa nói gì.
Cả hai đều đến từ cùng một chỗ. Hệ thống hook lớn hơn và kém đồng nhất hơn nhóm sự kiện ít ỏi mà phần lớn bài viết đề cập, còn quy tắc quyết định ai được nói với agent thì không phải là quy tắc bạn đoán ra. Đây là tài liệu tham chiếu mà chúng tôi từng ước có. Chúng tôi xây AgentsRoom trên chính các hook này, và mọi thứ bên dưới đều hoặc trích từ tài liệu chính thức, hoặc đo được trong môi trường sản xuất.
Có 30 sự kiện, không phải sáu
Phần lớn hướng dẫn nói về PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification và SubagentStop. Sáu sự kiện đó là thật, và chúng gánh phần lớn công việc hữu ích. Chúng cũng chỉ là một phần năm những gì đang tồn tại.
Danh sách đầy đủ, nhóm theo thứ mà chúng quan sát:
| Nhóm | Sự kiện |
|---|---|
| Phiên | SessionStart, SessionEnd, Setup |
| Prompt | UserPromptSubmit, UserPromptExpansion |
| Công cụ | PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch |
| Quyền | PermissionRequest, PermissionDenied |
| Lượt | Stop, StopFailure |
| Subagent và tác vụ | SubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle |
| Ngữ cảnh | PreCompact, PostCompact, InstructionsLoaded |
| Môi trường | FileChanged, CwdChanged, ConfigChange |
| Worktree | WorktreeCreate, WorktreeRemove |
| Giao diện | Notification, MessageDisplay |
| Elicitation MCP | Elicitation, ElicitationResult |

Thứ tự kích hoạt của các sự kiện trong một phiên. Khối công cụ lặp lại một lần cho mỗi lệnh gọi công cụ, và toàn bộ khối prompt lặp lại một lần cho mỗi lượt.
Một vài sự kiện trong số này thay đổi cách bạn nghĩ về hệ thống. PostToolUseFailure có tồn tại, nên nhánh "công cụ có chạy được không" là một sự kiện, không phải thứ bạn phải suy ra từ payload. PostToolBatch kích hoạt một lần sau khi một lô lệnh gọi công cụ song song hoàn tất, đó chính là chỗ đúng để chạy linter một lần thay vì một lần cho mỗi lần sửa. InstructionsLoaded kích hoạt khi CLAUDE.md được đọc, cho bạn một điểm móc để kiểm tra rằng agent thực sự đã nạp đúng bộ quy tắc bạn nghĩ nó đã nạp.
Quy tắc stdout nuốt chửng đầu ra của phần lớn hook
Đây là điều hữu ích nhất trên trang này.
Khi thoát với mã 0, Claude Code phân tích stdout để tìm các trường JSON đầu ra. Nhưng stdout đó có bao giờ được cho agent thấy hay không lại phụ thuộc vào sự kiện, và danh sách ngoại lệ rất ngắn. Trích từ tài liệu tham chiếu:
Với phần lớn sự kiện, stdout được ghi vào log debug nhưng không hiển thị trong transcript. Các ngoại lệ là
UserPromptSubmit,UserPromptExpansionvàSessionStart, nơi stdout được thêm vào như ngữ cảnh mà Claude có thể thấy và hành động dựa trên đó.
Ba sự kiện trên tổng số ba mươi. Nếu bạn echo "warning: this migration is destructive" từ một hook PostToolUse và mong agent đọc được, nó sẽ không bao giờ đọc. Văn bản của bạn đã đi vào log debug.
Có đúng hai cách đưa văn bản đến trước mắt agent từ một sự kiện bất kỳ nào khác:
- Thoát với mã 2 và ghi ra stderr. Khi thoát với mã 2, Claude Code bỏ qua stdout cùng mọi JSON nằm trong đó, và đưa stderr trở lại cho agent như một thông điệp lỗi.
- Thoát với mã 0 và in ra một đối tượng JSON mang
hookSpecificOutput.additionalContext.
Chú ý sự bất đối xứng ở cách thứ nhất. Thoát 0 nghĩa là stdout có giá trị còn stderr thì không. Thoát 2 nghĩa là stderr có giá trị còn stdout bị vứt bỏ hoàn toàn. Hiểu ngược điều này là lý do một hook trông hoàn toàn đúng mà vẫn câm lặng.
Mọi mã thoát khác đều là lỗi không chặn. Transcript hiển thị thông báo <hook name> hook error kèm dòng đầu tiên của stderr, quá trình thực thi tiếp tục, và toàn bộ stderr rơi vào log debug.

Kênh nào đến được agent, theo từng mã thoát. Đường nét đứt là đường mà người ta tưởng có nhưng thực ra không có.
Đúng một nửa có thể chặn
Mười lăm sự kiện dừng hành động khi thoát với mã 2. Mười lăm sự kiện bỏ qua và đi tiếp.
Có thể chặn: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.
Không thể chặn: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.
Hệ quả thực tế: hàng rào an toàn thuộc về PreToolUse, không bao giờ là PostToolUse. PostToolUse kích hoạt sau khi công cụ đã chạy xong thành công. Thoát với mã 2 ở đó không hoàn tác được thao tác ghi, nó chỉ in ra một lỗi trong khi thiệt hại đã nằm sẵn trên đĩa. Nếu bạn muốn chặn rm -rf, bạn có đúng một chỗ để làm việc đó.
Chuyện PostToolBatch chặn được trong khi PostToolUse thì không đáng để nhìn lại lần nữa. Nó có nghĩa là một kiểm tra ở cấp lô vẫn có thể dừng lượt sau khi các lần sửa song song đã ghi xuống, đây là thứ gần nhất với một quyền phủ quyết sau khi ghi mà hệ thống cung cấp.
Matcher so khớp chính xác, cho đến khi đột nhiên không còn thế
Trường matcher đổi chiến lược đánh giá dựa trên chính các ký tự của nó, và không có gì cho bạn biết nó đã đi theo nhánh nào.
| Matcher | Được đánh giá như |
|---|---|
"*", "", hoặc bỏ trống | khớp mọi thứ |
Chỉ có chữ cái, chữ số, _, -, dấu cách, ,, | | chuỗi chính xác, hoặc danh sách chuỗi chính xác tách theo | hoặc , |
| Bất kỳ thứ gì khác | biểu thức chính quy JavaScript không neo |
Không neo mới là cái bẫy. Tài liệu tham chiếu nói rõ rằng biểu thức được kiểm tra bằng RegExp.prototype.test, vốn thành công khi khớp ở bất kỳ đâu trong giá trị. Nên Edit.* khớp với Edit và NotebookEdit. Nếu bạn muốn nói đến đúng một công cụ, hãy viết ^Edit$.
Hai hành vi phụ thuộc phiên bản, nên biết trước khi bạn đi gỡ nhầm chỗ:
- Dấu phẩy làm ký tự phân tách và việc bỏ qua khoảng trắng cần Claude Code v2.1.191 trở lên.
- Dấu gạch nối gia nhập tập ký tự so khớp chính xác ở v2.1.195. Trước đó, một matcher như
code-reviewerbị xử lý như regex không neo, nên nó kích hoạt cả chosenior-code-reviewer.
Trong AgentsRoom, chúng tôi giới hạn hook gán tệp của mình bằng Write|Edit|MultiEdit|NotebookEdit, chuỗi này ở lại nhánh so khớp chính xác và chỉ khớp đúng bốn công cụ đó, không gì khác. Các hook vòng đời mà chúng tôi cài đặt hoàn toàn không có matcher, vì chúng lúc nào cũng liên quan đến chúng tôi.
Sáu nơi có thể định nghĩa hook, và chúng hợp nhất với nhau
Phản xạ đầu tiên là đi tìm thứ tự ưu tiên. Không có thứ tự nào cả, và đó mới là phần thú vị.
| Vị trí | Phạm vi |
|---|---|
~/.claude/settings.json | mọi dự án của bạn, cục bộ trên máy bạn |
.claude/settings.json | một dự án, commit được |
.claude/settings.local.json | một dự án, được Claude Code gitignore |
| Managed policy settings | toàn tổ chức, do quản trị viên kiểm soát |
hooks/hooks.json của plugin | trong khi plugin còn được bật |
| Frontmatter của skill hoặc agent | trong khi thành phần đó còn hoạt động |
Trích từ tài liệu tham chiếu:
Các mục hook hợp nhất qua các cấp settings thay vì thay thế lẫn nhau: settings người dùng, dự án và cục bộ bổ sung hook của riêng chúng mà không gỡ bỏ các hook được quản lý, và thiết lập
disableAllHookskhông thể tắt các hook được quản lý từ bên ngoài phạm vi settings được quản lý.
Vậy nên một hook của dự án không bao giờ ghi đè hook toàn cục, nó chồng lên trên. Sáu nguồn, tất cả đều cộng dồn. Một trình định dạng PostToolUse khai báo trong settings người dùng rồi khai báo lại trong dự án sẽ chạy hai lần cho mỗi lần sửa, và triệu chứng duy nhất là cảm giác mọi thứ chậm đi.

Sáu nguồn, một tập hợp nhất. Không có gì ở đây ghi đè thứ gì.
Điều này cũng giải thích vì sao .claude/settings.local.json là chỗ đúng để một công cụ cài hook vào dự án của người khác. Nó giới hạn trong phạm vi dự án, Claude Code gitignore nó, và nó được nạp mà không cần cờ dòng lệnh nào. Đó là nơi AgentsRoom ghi các mục của mình, để tệp .claude/settings.json đã commit của người dùng không bao giờ bị đụng tới và đồng nghiệp của họ không bao giờ thừa hưởng một đường dẫn gắn với máy cụ thể.
Chạy hook trong môi trường sản xuất đã dạy chúng tôi điều gì
AgentsRoom cài hook vào mọi dự án mà nó mở, để theo dõi trạng thái agent một cách xác định và gán tệp đã sửa cho đúng agent. Có vài chuyện chỉ lộ ra ở quy mô đó.
Tên sự kiện lạ bị bỏ qua trong im lặng. Điều này không có trong tài liệu, và chúng tôi phụ thuộc vào nó. Khi chúng tôi thêm một sự kiện vòng đời mới vào trình cài đặt, người dùng đang chạy CLI phiên bản cũ sẽ nhận một tệp settings.local.json chứa tên sự kiện mà binary của họ chưa từng nghe đến. Không có gì hỏng, không có gì cảnh báo, mục đó bị bỏ qua. Chính điều đó khiến trình cài đặt an toàn để phát hành trước một bản CLI. Và tất nhiên, đó cũng là lý do một lỗi gõ nhầm tạo ra sự im lặng tuyệt đối chứ không phải một lỗi.
agent_id là cách bạn biết mình đang ở trong một subagent. Trường này chỉ có mặt khi hook kích hoạt bên trong một lệnh gọi subagent. Chuyện này quan trọng hơn vẻ ngoài của nó: Stop kích hoạt khi một subagent kết thúc lượt của mình, chứ không riêng gì agent chính. Một quy tắc ngây thơ kiểu "đánh dấu phiên đã xong khi gặp Stop" sẽ đánh dấu cả phiên là kết thúc ngay lần đầu tiên có một subagent trả về. Chúng tôi bỏ qua các sự kiện kết thúc lượt có mang agent_id đúng vì lý do này.
Đừng đọc transcript_path cho lượt đang chạy. Tài liệu tham chiếu cảnh báo rằng transcript được ghi bất đồng bộ và có thể chậm hơn cuộc hội thoại đang nằm trong bộ nhớ, nên các tin nhắn gần nhất có thể chưa có ở đó khi hook của bạn kích hoạt. Stop và SubagentStop nhận last_assistant_message chính là để bạn không bao giờ phải chạy đua với tệp.
Hook là tín hiệu trạng thái đáng tin cậy duy nhất. Trước khi có hook, chúng tôi bóc tách PTY để đoán xem agent đang suy nghĩ, đang chờ hay đã xong. Cách đó hỏng ngay khi CLI vẽ giao diện qua bộ đệm màn hình phụ của terminal, đúng thứ mà /tui fullscreen làm. Hook kích hoạt y hệt nhau dưới mọi bộ dựng hình. Nếu bạn đang xây bất cứ thứ gì quan sát một agent từ bên ngoài, đây là lớp nên xây dựng lên trên, còn việc bóc tách màn hình thì cùng lắm chỉ nên giữ làm phương án dự phòng.
async: true không tốn gì cả. Một lệnh hook có thể khai báo async: true, và agent sẽ không chờ nó. Hook của chúng tôi gửi POST đến một endpoint cục bộ với giới hạn 2 giây rồi trả quyền điều khiển; độ trễ lượt của agent không hề bị ảnh hưởng, kể cả khi ứng dụng nhận đã đóng. Nếu hook của bạn chỉ quan sát mà không bao giờ quyết định, hãy chuyển nó sang async và thôi phải trả giá cho nó.
Đừng bao giờ để hook viết rác ra terminal. Script của chúng tôi nuốt mọi ngoại lệ, kể cả ở cấp cao nhất. Một traceback Python không được bắt phát ra từ hook không chỉ thất bại lặng lẽ, nó in cả stack trace Python vào phiên terminal của người dùng, ngay giữa lúc họ đang làm việc.
Thời gian chờ
Giá trị mặc định khá rộng rãi, trừ ba ngoại lệ thì không:
| Loại hook | Thời gian chờ mặc định |
|---|---|
command, http, mcp_tool | 600 s |
prompt | 30 s |
agent | 60 s |
UserPromptSubmit (command, http, mcp_tool) | 30 s |
MessageDisplay (command, http, mcp_tool) | 10 s |
SessionEnd | 1.5 s dùng chung cho tất cả hook, được nâng lên cho khớp với một timeout dài hơn ở từng hook, tối đa 60 s |
Ngân sách của SessionEnd là thứ khiến người ta bất ngờ. Đó là ngân sách dùng chung, không phải hạn mức cho từng hook, nên ba hook dọn dẹp sẽ chia nhau 1.5 giây trừ khi bạn nâng nó lên một cách tường minh.
Bản rút gọn
- Có 30 sự kiện. Sáu trong số đó nổi tiếng.
- stdout chỉ đến được agent trên
UserPromptSubmit,UserPromptExpansionvàSessionStart. Ở mọi nơi khác, hãy dùng thoát 2 với stderr, hoặcadditionalContexttrong JSON. - 15 sự kiện chặn khi thoát 2, 15 sự kiện bỏ qua. Hàng rào an toàn đặt trên
PreToolUse. - Matcher là chuỗi chính xác cho đến khi một ký tự đặc biệt biến nó thành regex không neo.
- Sáu nguồn settings hợp nhất theo kiểu cộng dồn. Không có gì ghi đè thứ gì.
- Một tên sự kiện viết sai chính tả thất bại trong im lặng hoàn toàn.
Nếu bạn muốn tận mắt nhìn các sự kiện này kích hoạt thay vì ngồi suy luận về chúng, đó chính là thứ chúng tôi đã xây: AgentsRoom hiển thị mọi lần kích hoạt hook theo từng agent, từng dự án, từng lần chạy, trên hàng chục agent chạy song song và các phiên subagent. Các hook bạn tự cấu hình trong settings của mình vẫn chạy đúng như đã viết, vì nó chạy CLI thật.
Đọc thêm
Điều Khiển Các Agent Lập Trình Từ Điện Thoại Khi Chúng Làm Việc
Các agent của bạn lập trình liên tục trong 20 phút. Bạn không cần ngồi trước màn hình. Đây là cách theo dõi và hỗ trợ chúng từ điện thoại của bạn.
Đọc bài viếtHow to Scale AI Coding Agents Across a Dev Team
One developer with a coding agent is a productivity story. Five developers with twenty agents is a coordination problem. Here is what breaks first when a team scales up, and the setup that holds: committed context files, clear file ownership, review by blast radius, and cost you can actually see.
Đọc bài viếtBạn có nên xem lại mã của AI Agent không?
Các agent của bạn viết mã tốt hơn một nửa các pull request mà bạn từng hợp nhất. Vậy bạn có còn đọc từng dòng không? Lập luận trung thực cho cả hai bên, 10 tín hiệu cho thấy một agent đã mắc lỗi, và mỗi thay đổi thực sự xứng đáng được xem xét bao nhiêu.
Đọc bài viết
Tải AgentsRoom
Chạy các agent AI của bạn (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) trên tất cả dự án, trong một cửa sổ duy nhất.
Ứng dụng đồng hành: theo dõi agent khi đi đường
Sử dụng Claude, Codex, Antigravity CLI hoặc nhà cung cấp AI khác.
Gửi lỗi và yêu cầu thẳng vào backlog công khai của bạn.
Một cái nhìn về AgentsRoom đang hoạt động.