Hooks là lệnh shell, HTTP endpoint, hoặc LLM prompt do bạn định nghĩa, chạy tự động tại các điểm cụ thể trong vòng đời của Claude Code.
Vòng đời hook
Phần tiêu đề “Vòng đời hook”Claude Code chạy hooks tại các điểm cụ thể trong một phiên. Khi một sự kiện xảy ra và một matcher khớp, Claude Code truyền ngữ cảnh JSON về sự kiện đó cho handler của bạn. Với command hook, input tới qua stdin. Với HTTP hook, nó tới dưới dạng phần thân của request POST. Handler của bạn có thể kiểm tra input, thực hiện hành động, và tùy chọn trả về một quyết định.
Các sự kiện có ba nhịp độ:
- một lần mỗi phiên:
SessionStartvàSessionEnd - một lần mỗi lượt:
UserPromptSubmit,Stop, vàStopFailure - mỗi lần tool được gọi trong vòng lặp agentic:
PreToolUsevàPostToolUse, ngoại trừ lệnh gọiEndConversationbỏ qua cả hai
Bảng dưới đây tóm tắt thời điểm mỗi sự kiện xảy ra. Phần Sự kiện hook bên dưới ghi đầy đủ input schema và tùy chọn kiểm soát quyết định cho từng sự kiện.
| Sự kiện | Thời điểm chạy |
|---|---|
SessionStart | Khi một phiên bắt đầu hoặc được resume |
Setup | Khi bạn khởi động Claude Code với --init-only, hoặc với --init/--maintenance ở chế độ -p. Dùng để chuẩn bị một lần trong CI hoặc script |
UserPromptSubmit | Khi bạn gửi một 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 lệnh gọi tool thực thi. Có thể chặn |
PermissionRequest | Khi một lệnh gọi tool cần quyết định về quyền |
PermissionDenied | Khi một lệnh gọi tool bị bộ phân loại của auto mode từ chối. Trả về {retry: true} để báo model có thể thử lại |
PostToolUse | Sau khi một lệnh gọi tool thành công |
PostToolUseFailure | Sau khi một lệnh gọi tool thất bại |
PostToolBatch | Sau khi toàn bộ một batch tool call song song hoàn tất, trước lệnh gọi model tiếp theo |
Notification | Khi Claude Code gửi thông báo |
MessageDisplay | Trong lúc text của tin nhắn assistant đang được hiển thị |
SubagentStart | Khi một subagent được sinh ra |
SubagentStop | Khi một subagent hoàn tất |
TaskCreated | Khi một task đang được tạo qua TaskCreate |
TaskCompleted | Khi một task đang được đánh dấu hoàn thành |
Stop | Khi Claude kết thúc lượt trả lời |
StopFailure | Khi lượt trả lời kết thúc do lỗi API. Output và exit code bị bỏ qua |
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 context. Chạy khi phiên bắt đầu và khi file được nạp muộn (lazy) trong phiên |
ConfigChange | Khi một file cấu hình thay đổi trong lúc phiên đang chạy |
CwdChanged | Khi thư mục làm việc thay đổi, ví dụ khi Claude chạy lệnh cd. Hữu ích cho quản lý môi trường phản ứng (reactive) với công cụ như direnv |
FileChanged | Khi một file đang được theo dõi thay đổi trên đĩa. Field matcher quy định tên file nào cần theo dõi |
WorktreeCreate | Khi một worktree đang được tạo qua --worktree, isolation: "worktree", hoặc cho một phiên background. Thay thế hành vi git mặc định |
WorktreeRemove | Khi một worktree đang bị xóa lúc phiên kết thúc, khi một subagent hoàn tất, hoặc khi bạn xóa một phiên background |
PreCompact | Trước khi nén context |
PostCompact | Sau khi nén context hoàn tất |
Elicitation | Khi một MCP server yêu cầu input từ người dùng trong lúc gọi tool |
ElicitationResult | Sau khi người dùng trả lời một elicitation MCP, trước khi phản hồi được gửi lại cho server |
SessionEnd | Khi một phiên kết thúc |
Cách một hook được giải quyết
Phần tiêu đề “Cách một hook được giải quyết”Để thấy các mảnh ghép này khớp với nhau ra sao, xét ví dụ một PreToolUse hook chặn lệnh shell mang tính phá hoại. matcher thu hẹp về lệnh gọi tool Bash, và điều kiện if thu hẹp thêm về các subcommand Bash khớp rm *, nên block-rm.sh chỉ chạy khi cả hai bộ lọc cùng khớp:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(rm *)", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh", "args": [] } ] } ] }}Script đọc JSON input từ stdin, trích lệnh, và trả về permissionDecision là "deny" nếu nó chứa rm -rf. Lưu vào .claude/hooks/block-rm.sh trong dự án của bạn:
#!/bin/bashCOMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Destructive command blocked by hook" } }'else exit 0 # không có quyết định; luồng permission bình thường áp dụngfiTrên macOS và Linux, cấp quyền thực thi bằng chmod +x .claude/hooks/block-rm.sh. Trên Windows, viết hook bằng PowerShell và đăng ký với "command": "powershell.exe" (xem ví dụ ở phần MessageDisplay).
Script này và các ví dụ Bash khác trên trang phân tích JSON input đều dùng jq, nên cài jq và đảm bảo nó nằm trong PATH trước khi thử.
Giả sử Claude Code quyết định chạy Bash "rm -rf /tmp/build". Quy trình diễn ra như sau:
- Sự kiện xảy ra -
PreToolUsechạy, Claude Code gửi tool input dạng JSON qua stdin cho hook:{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... } - Matcher kiểm tra - matcher
"Bash"khớp tên tool nên nhóm hook này kích hoạt. Nếu bỏ qua matcher hoặc dùng"*", nhóm kích hoạt ở mọi lần xảy ra sự kiện. - Điều kiện
ifkiểm tra -"Bash(rm *)"khớp vìrm -rf /tmp/buildlà subcommand khớprm *, nên handler này chạy. Nếu lệnh lànpm test, kiểm traifsẽ không khớp vàblock-rm.shsẽ không chạy, tránh chi phí spawn tiến trình. Fieldiflà tùy chọn; không có nó, mọi handler trong nhóm khớp đều chạy. - Handler hook chạy - script kiểm tra toàn bộ lệnh, thấy
rm -rf, và in ra quyết định ở stdout (JSONpermissionDecision: "deny"như trên). Nếu lệnh là biến thể an toàn hơn nhưrm file.txt, script sẽ chạyexit 0thay vào đó. Exit code 0 không kèm output nghĩa là hook không có quyết định để báo cáo, nên lệnh gọi tool tiếp tục qua luồng permission bình thường. Hook có thể từ chối lệnh gọi, nhưng im lặng không đồng nghĩa với phê duyệt. - Claude Code hành động theo kết quả - Claude Code đọc quyết định JSON, chặn lệnh gọi tool, và cho Claude thấy lý do.
Phần Cấu hình bên dưới ghi đầy đủ schema, và mỗi phần sự kiện hook mô tả input mà lệnh của bạn nhận và output nó có thể trả về.
Cấu hình
Phần tiêu đề “Cấu hình”Hooks được định nghĩa trong các file settings JSON. Cấu hình có ba tầng lồng nhau:
- Chọn một sự kiện hook để phản ứng, như
PreToolUsehayStop - Thêm một nhóm matcher để lọc thời điểm nó chạy, ví dụ “chỉ cho tool Bash”
- Định nghĩa một hoặc nhiều hook handler chạy khi khớp
Xem Cách một hook được giải quyết ở trên để có một ví dụ đầy đủ, có chú giải.
Vị trí đặt hook
Phần tiêu đề “Vị trí đặt hook”Nơi bạn định nghĩa một hook quyết định phạm vi của nó:
| Vị trí | Phạm vi | Chia sẻ được không |
|---|---|---|
~/.claude/settings.json | Mọi dự án của bạn | Không, chỉ máy này |
.claude/settings.json | Một dự án | Có, commit được vào repo |
.claude/settings.local.json | Một dự án | Không, gitignore khi Claude Code lưu setting vào đó |
| 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 hoặc agent | Trong lúc component đang hoạt động | Có, định nghĩa trong file component |
Xem thêm settings để biết chi tiết cách các file settings được phân giải.
Hooks từ file settings, managed policy settings, và plugin cũng chạy bên trong subagent. Khi một subagent gọi tool, các sự kiện tool như PreToolUse và PostToolUse chạy cùng hooks đã cấu hình như trong hội thoại chính, và input mang thêm field agent_id và agent_type (xem field input chung) để nhận diện subagent.
Quản trị viên enterprise có thể dùng allowManagedHooksOnly để chặn hooks từ user, project, và plugin. Hooks từ plugin được ép bật trong managed settings qua enabledPlugins được miễn trừ, nên admin có thể phân phối hooks đã kiểm duyệt qua một marketplace nội bộ tổ chức.
Các mục hook được gộp giữa các tầng settings thay vì thay thế lẫn nhau: settings ở user, project, local thêm hooks riêng mà không xóa hooks managed, và setting disableAllHooks không thể tắt hooks managed nếu không đặt ở chính tầng managed.
Allowlist HTTP hook áp dụng cho hooks từ mọi nguồn, kể cả managed policy settings:
allowedHttpHookUrls: khi được định nghĩa ở bất kỳ tầng settings nào, Claude Code chỉ chạy một HTTP hook handler nếu URL của nó khớp allowlist đã gộphttpHookAllowedEnvVars: khi được định nghĩa, Claude Code chỉ nội suy các biến môi trường nằm trong danh sách đó vào header hook
Mẫu matcher
Phần tiêu đề “Mẫu matcher”Field matcher lọc thời điểm hooks chạy. Cách một matcher được đánh giá phụ thuộc vào ký tự nó chứa:
| Giá trị matcher | Được đánh giá như | Ví dụ |
|---|---|---|
"*", "", hoặc bỏ trống | Khớp tất cả | chạy ở mọi lần xảy ra sự kiện |
Chỉ chữ, số, _, -, khoảng trắng, ,, và | | Chuỗi chính xác, hoặc danh sách chuỗi chính xác ngăn bởi | hoặc , (khoảng trắng bao quanh được cho phép) | Bash chỉ khớp tool Bash; Edit|Write và Edit, Write đều khớp một trong hai tool; code-reviewer chỉ khớp đúng loại agent đó |
| Chứa ký tự khác | Regular expression JavaScript, không neo (unanchored) | ^Notebook khớp mọi tool có tên bắt đầu bằng Notebook; mcp__memory__.* khớp mọi tool từ server memory |
Một matcher trên nhánh regex được kiểm bằng RegExp.prototype.test của JavaScript, khớp thành công khi có khớp ở bất kỳ đâu trong giá trị. Edit.* khớp cả Edit lẫn NotebookEdit; bọc mẫu trong ^ và $, như ^Edit$, khi cần khớp toàn chuỗi.
Dấu phẩy làm ký tự phân tách và khoảng trắng bao quanh được cho phép yêu cầu Claude Code v2.1.191 trở lên. Dấu gạch ngang trong tập ký tự khớp-chính-xác yêu cầu v2.1.195 trở lên - ở phiên bản cũ hơn, một tên có gạch ngang như code-reviewer được đánh giá như regex không neo, nên cũng khớp senior-code-reviewer; neo lại bằng ^code-reviewer$ để chỉ khớp đúng tên đó.
FileChanged và StopFailure dùng tập khớp-chính-xác hẹp hơn: chỉ chữ, số, _, và |. Dấu gạch ngang, khoảng trắng, hoặc dấu phẩy trong matcher của hai sự kiện này khiến nó rơi vào nhánh regex, và chỉ | phân tách các lựa chọn. Mọi sự kiện khác có hỗ trợ matcher trong bảng dưới đây chấp nhận cả | lẫn ,.
Sự kiện FileChanged không theo các quy tắc này khi xây danh sách theo dõi - xem FileChanged.
Mỗi loại sự kiện khớp trên một field khác nhau:
| Sự kiện | Matcher lọc theo | Ví dụ giá trị matcher |
|---|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied | tên tool | Bash, Edit|Write, mcp__.* |
SessionStart | cách phiên bắt đầu | startup, resume, clear, compact, fork |
Setup | flag CLI nào kích hoạt setup | init, maintenance |
SessionEnd | lý do phiên kết thúc | clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other |
Notification | loại thông báo | permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed |
SubagentStart | loại agent | general-purpose, Explore, Plan, tên agent tùy chỉnh, hoặc tên phạm vi plugin như ^my-plugin:reviewer$ |
PreCompact, PostCompact | điều gì kích hoạt nén | manual, auto |
SubagentStop | loại agent | giống giá trị của SubagentStart |
ConfigChange | nguồn cấu hình | user_settings, project_settings, local_settings, policy_settings, skills |
CwdChanged | không hỗ trợ matcher | luôn chạy ở mọi lần đổi thư mục |
FileChanged | tên file literal cần theo dõi (xem FileChanged) | .envrc|.env |
StopFailure | loại lỗi | rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, unknown |
InstructionsLoaded | lý do nạp | session_start, nested_traversal, path_glob_match, include, compact |
UserPromptExpansion | tên lệnh | tên skill hoặc command của bạn |
Elicitation | tên MCP server | tên MCP server bạn đã cấu hình |
ElicitationResult | tên MCP server | giống Elicitation |
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay | không hỗ trợ matcher | luôn chạy ở mọi lần xảy ra |
Matcher chạy trên một field trích từ JSON input mà Claude Code gửi cho hook qua stdin. Với sự kiện tool, field đó là tool_name. Mỗi phần sự kiện hook liệt kê đầy đủ giá trị matcher và input schema cho sự kiện đó.
Ví dụ này chỉ chạy script lint khi Claude ghi hoặc sửa một file:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "/path/to/lint-check.sh" } ] } ] }}UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay, và CwdChanged không hỗ trợ matcher và luôn chạy ở mọi lần xảy ra. Nếu bạn thêm field matcher vào các sự kiện này, nó bị bỏ qua âm thầm.
Với sự kiện tool, bạn có thể lọc hẹp hơn bằng field if trên từng hook handler. if dùng cú pháp quy tắc quyền để khớp cả tên tool lẫn tham số cùng lúc, nên "Bash(git *)" chạy khi bất kỳ subcommand nào của Bash input khớp git *, và "Edit(*.ts)" chỉ chạy cho file TypeScript.
Khớp MCP tool
Phần tiêu đề “Khớp MCP tool”Tool từ MCP server xuất hiện như tool thông thường trong các sự kiện tool (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), nên bạn khớp chúng giống như bất kỳ tool nào khác.
Tool MCP theo mẫu đặt tên mcp__<server>__<tool>, ví dụ:
mcp__memory__create_entities: tool tạo entity của server Memorymcp__filesystem__read_file: tool đọc file của server Filesystemmcp__github__search_repositories: tool tìm kiếm của server GitHub
Để khớp mọi tool từ một server, thêm .* vào sau tiền tố server. .* là bắt buộc: một matcher như mcp__memory hay mcp__brave-search chỉ chứa ký tự khớp-chính-xác, nên được so sánh như chuỗi chính xác và không khớp tool nào.
mcp__memory__.*khớp mọi tool từ servermemorymcp__brave-search__.*khớp mọi tool từ một server có tên chứa gạch ngangmcp__.*__write.*khớp bất kỳ tool nào có tên bắt đầu bằngwritetừ mọi server
Dấu gạch ngang trong tập khớp-chính-xác yêu cầu v2.1.195 trở lên; ở phiên bản cũ hơn một tiền tố có gạch ngang trần như mcp__brave-search được đánh giá như regex không neo và khớp mọi tool từ server đó. Dạng mcp__brave-search__.* hoạt động ở mọi phiên bản.
Tool từ một MCP server đóng gói trong plugin dùng đoạn tên server có phạm vi gồm cả tên plugin: mcp__plugin_<tên-plugin>_<tên-server>__<tool>. Matcher viết theo server key trần sẽ không bao giờ khớp các tool này. Với plugin tên my-plugin đóng gói server dưới key db, tool query xuất hiện dưới dạng mcp__plugin_my-plugin_db__query, nên matcher cho mọi tool từ server đó là mcp__plugin_my-plugin_db__.*. Dùng cùng tên tool có phạm vi này trong field if của handler.
Ví dụ này ghi log mọi thao tác của server memory và kiểm tra thao tác ghi từ bất kỳ MCP server nào:
{ "hooks": { "PreToolUse": [ { "matcher": "mcp__memory__.*", "hooks": [ { "type": "command", "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log" } ] }, { "matcher": "mcp__.*__write.*", "hooks": [ { "type": "command", "command": "/home/user/scripts/validate-mcp-write.py" } ] } ] }}Field của hook handler
Phần tiêu đề “Field của hook handler”Mỗi object trong mảng hooks bên trong là một hook handler: lệnh shell, HTTP endpoint, MCP tool, LLM prompt, hoặc agent chạy khi matcher khớp. Có năm loại:
- Command hook (
type: "command"): chạy một lệnh shell. Script của bạn nhận JSON input của sự kiện qua stdin và báo kết quả qua exit code cùng stdout. - HTTP hook (
type: "http"): gửi JSON input của sự kiện dưới dạng request POST tới một URL. Endpoint báo kết quả qua response body, dùng cùng định dạng JSON output như command hook. - MCP tool hook (
type: "mcp_tool"): gọi một tool trên MCP server đã kết nối sẵn. Text output của tool được xử lý như stdout của command hook. - Prompt hook (
type: "prompt"): gửi một prompt tới model Claude để đánh giá một lượt. Model trả về quyết định có/không dạng JSON. Xem Hook dựa trên prompt. - Agent hook (
type: "agent"): sinh một subagent có thể dùng tool như Read, Grep, Glob để xác minh điều kiện trước khi trả quyết định. Agent hook đang ở dạng thử nghiệm, có thể thay đổi. Xem Hook dựa trên agent.
Mọi hook khớp chạy song song, và các handler giống hệt nhau được loại trùng tự động. Command hook loại trùng theo chuỗi command và args; HTTP hook loại trùng theo URL.
Handler chạy trong thư mục hiện tại với môi trường của Claude Code. Biến $CLAUDE_CODE_REMOTE được đặt "true" trong môi trường web từ xa và không được đặt trong CLI local. Kể từ v2.1.199, $CLAUDE_CODE_BRIDGE_SESSION_ID được đặt thành ID phiên Remote Control khi phiên local có kết nối Remote Control đang hoạt động.
Field chung
Phần tiêu đề “Field chung”Các field này áp dụng cho mọi loại hook:
| Field | Bắt buộc | Mô tả |
|---|---|---|
type | có | "command", "http", "mcp_tool", "prompt", hoặc "agent" |
if | không | Cú pháp quy tắc quyền để lọc thời điểm hook này chạy, như "Bash(git *)" hay "Edit(*.ts)". Lệnh hook chỉ chạy khi lệnh gọi tool khớp mẫu. Xem bảng khớp Bash bên dưới về cách mẫu Bash được đánh giá với subcommand, $(), và backtick. Chỉ được đánh giá trên sự kiện tool: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, và PermissionDenied. Ở sự kiện khác, một hook có đặt if sẽ không bao giờ chạy. Dùng cùng cú pháp với quy tắc quyền |
timeout | không | Số giây trước khi hủy. Mặc định: 600 cho command, http, mcp_tool; 30 cho prompt; 60 cho agent. UserPromptSubmit hạ mặc định của command, http, mcp_tool xuống 30, và MessageDisplay hạ xuống 10. Hooks của SessionEnd chia sẻ ngân sách 1,5 giây; nếu settings đặt timeout dài hơn cho từng hook, Claude Code nâng ngân sách lên tương ứng, tối đa 60 giây |
statusMessage | không | Thông điệp spinner tùy chỉnh hiển thị trong lúc hook chạy |
once | không | Nếu true, chạy một lần mỗi phiên rồi bị gỡ bỏ. Chỉ có hiệu lực với hooks khai báo trong frontmatter của skill; bị bỏ qua trong file settings và frontmatter agent |
Field if chỉ chứa đúng một quy tắc quyền. Không có &&, ||, hay cú pháp danh sách để kết hợp quy tắc; muốn áp nhiều điều kiện thì định nghĩa một handler riêng cho mỗi điều kiện.
Trong một điều kiện if cho tool file, một mẫu thư mục một-đoạn như "Edit(src/**)" chỉ khớp thư mục src ngay trong thư mục làm việc và các file bên dưới nó. Để khớp thư mục tên src ở bất kỳ độ sâu nào, viết "Edit(**/src/**)". Trước v2.1.214, "Edit(src/**)" khớp thư mục tên src ở bất kỳ độ sâu nào dưới thư mục làm việc.
Mẫu if | Lệnh Bash | Hook có chạy? | Vì sao |
|---|---|---|---|
Bash(git *) | FOO=bar git push | có | phép gán đầu bị loại; git push khớp |
Bash(git *) | npm test && git push | có | mỗi subcommand được kiểm tra; git push khớp |
Bash(rm *) | echo $(rm -rf /) | có | lệnh bên trong $() và backtick cũng được kiểm tra; rm -rf / khớp |
Bash(rm *) | echo $(date) | không | không có subcommand nào khớp rm * |
Bash(git push *) | echo $(date) | có | mẫu chỉ định nhiều hơn tên lệnh vẫn chạy hook trên $(), backtick, hoặc $VAR |
Bộ lọc cũng “fail open” - chạy hook của bạn bất kể mẫu - khi không phân tích được lệnh Bash. Vì if chỉ mang tính cố gắng tốt nhất (best-effort), hãy dùng hệ thống quyền thay vì hook để ép buộc allow/deny cứng.
Field command hook
Phần tiêu đề “Field command hook”Ngoài field chung, command hook chấp nhận các field sau:
| Field | Bắt buộc | Mô tả |
|---|---|---|
command | có | Lệnh shell để chạy. Kèm args, đây là executable được spawn trực tiếp. Xem Dạng exec và dạng shell |
args | không | Danh sách tham số. Khi có, command được phân giải như một executable và spawn trực tiếp với args làm vector tham số, không qua shell. Xem Dạng exec và dạng shell |
async | không | Nếu true, chạy ở background không chặn luồng chính. Xem Chạy hook ở background |
asyncRewake | không | Nếu true, chạy ở background và đánh thức Claude khi exit code là 2. Ngầm định async. Stderr của hook (hoặc stdout nếu stderr rỗng) được hiện cho Claude dưới dạng system reminder để nó phản ứng với lỗi background chạy lâu |
shell | không | Shell dùng cho hook này. Nhận "bash" hoặc "powershell". Mặc định "bash", hoặc "powershell" trên Windows khi không cài Git Bash. Đặt "powershell" chạy lệnh qua PowerShell trên Windows. Không cần CLAUDE_CODE_USE_POWERSHELL_TOOL vì hooks spawn PowerShell trực tiếp. Bị bỏ qua khi có args |
Một command hook chạy ở dạng exec khi có args, và dạng shell khi không có args. Đặt args bất cứ khi nào hook tham chiếu tới một placeholder đường dẫn, vì mỗi phần tử được truyền như một tham số riêng, không cần quote. Bỏ args khi bạn cần tính năng shell như pipe hay &&, hoặc khi không cần cả hai điều trên.
Dạng exec chạy khi có args. Claude Code phân giải command như một executable trên PATH và spawn trực tiếp với args làm vector tham số. Không có shell, nên mỗi phần tử args là đúng một tham số như đã viết, và các placeholder đường dẫn như ${CLAUDE_PLUGIN_ROOT} được thay thế vào command và từng phần tử args như chuỗi trần. Ký tự đặc biệt như dấu nháy đơn, $, backtick đi qua nguyên vẹn vì không có shell diễn giải chúng. Không có tokenization của shell trên bất kỳ nền tảng nào.
Dạng shell chạy khi không có args. Chuỗi command được truyền cho một shell: sh -c trên macOS và Linux, Git Bash trên Windows, hoặc PowerShell khi không cài Git Bash. Đặt field shell để chọn tường minh. Shell tokenize chuỗi, mở rộng biến, và diễn giải pipe, &&, redirect, glob.
Ví dụ chạy một script Node đóng gói cùng plugin. Dạng exec truyền đường dẫn script đã phân giải như một tham số duy nhất, không cần quote:
{ "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]}Dạng shell tương đương cần quote để xử lý đường dẫn có khoảng trắng hoặc ký tự đặc biệt:
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"}Cả hai dạng đều hỗ trợ cùng placeholder đường dẫn, và cả hai đều export chúng dưới dạng biến môi trường CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, và CLAUDE_PLUGIN_DATA trên tiến trình được spawn, nên script có thể đọc process.env.CLAUDE_PLUGIN_ROOT bất kể được khởi chạy cách nào.
Plugin hook còn thay thế thêm giá trị ${user_config.*}, chỉ ở dạng exec: giá trị được thay vào command và từng phần tử args như chuỗi trần, nên không có shell nào phân tích lại nó.
Một plugin hook dạng shell mà command tham chiếu ${user_config.*} sẽ lỗi thay vì chạy. Để dùng giá trị option từ một hook dạng shell, đọc biến môi trường $CLAUDE_PLUGIN_OPTION_<KEY>, ví dụ $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL cho option webhook_url, hoặc đặt args để chuyển hook sang dạng exec. Trước v2.1.207, lệnh plugin hook dạng shell cũng thay thế ${user_config.*}.
Field HTTP hook
Phần tiêu đề “Field HTTP hook”Ngoài field chung, HTTP hook chấp nhận các field sau:
| Field | Bắt buộc | Mô tả |
|---|---|---|
url | có | URL để gửi request POST |
headers | không | Header HTTP bổ sung dạng cặp key-value. Giá trị hỗ trợ nội suy biến môi trường qua cú pháp $VAR_NAME hoặc ${VAR_NAME}. Chỉ biến nằm trong allowedEnvVars mới được phân giải |
allowedEnvVars | không | Danh sách tên biến môi trường được phép nội suy vào giá trị header. Tham chiếu tới biến không nằm trong danh sách bị thay bằng chuỗi rỗng. Bắt buộc để nội suy biến môi trường hoạt động |
Claude Code gửi JSON input của hook làm body của request POST với Content-Type: application/json. Response body dùng cùng định dạng JSON output như command hook.
Xử lý lỗi khác với command hook: response không phải 2xx, lỗi kết nối, và timeout đều tạo ra lỗi không-chặn (non-blocking), cho phép thực thi tiếp tục. Để chặn một lệnh gọi tool hoặc từ chối một quyền, trả về response 2xx với body JSON chứa decision: "block" hoặc hookSpecificOutput với permissionDecision: "deny".
Ví dụ này gửi sự kiện PreToolUse tới một dịch vụ xác thực nội bộ, xác thực bằng token từ biến môi trường MY_TOKEN:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "http", "url": "http://localhost:8080/hooks/pre-tool-use", "timeout": 30, "headers": { "Authorization": "Bearer $MY_TOKEN" }, "allowedEnvVars": ["MY_TOKEN"] } ] } ] }}Field MCP tool hook
Phần tiêu đề “Field MCP tool hook”Ngoài field chung, MCP tool hook chấp nhận các field sau:
| Field | Bắt buộc | Mô tả |
|---|---|---|
server | có | Tên một MCP server đã cấu hình. Với server đóng gói trong plugin, đây là tên có phạm vi plugin:<tên-plugin>:<tên-server>, ví dụ plugin:my-plugin:db, không phải server key trần. Server phải đã kết nối sẵn; hook không bao giờ tự kích hoạt luồng OAuth hay kết nối |
tool | có | Tên tool cần gọi trên server đó |
input | không | Tham số truyền cho tool. Giá trị chuỗi hỗ trợ thay thế ${path} từ JSON input của hook, ví dụ "${tool_input.file_path}" |
Nội dung text của tool được xử lý như stdout của command hook: nếu nó phân tích được thành JSON output hợp lệ thì được xử lý như một quyết định, ngược lại nó được hiển thị như văn bản thuần. Nếu server được nêu tên chưa kết nối, hoặc tool trả về isError: true, hook tạo ra lỗi không-chặn và thực thi tiếp tục.
MCP tool hook khả dụng trên mọi sự kiện hook, sau khi Claude Code đã kết nối tới MCP server của bạn. SessionStart và Setup thường xảy ra trước khi server kết nối xong, nên hooks trên các sự kiện đó nên lường trước lỗi “not connected” ở lần chạy đầu.
Ví dụ này gọi tool security_scan trên MCP server my_server sau mỗi lần Write hoặc Edit, truyền đường dẫn file đã sửa:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "mcp_tool", "server": "my_server", "tool": "security_scan", "input": { "file_path": "${tool_input.file_path}" } } ] } ] }}Field prompt và agent hook
Phần tiêu đề “Field prompt và agent hook”Ngoài field chung, prompt hook và agent hook chấp nhận các field sau:
| Field | Bắt buộc | Mô tả |
|---|---|---|
prompt | có | Nội dung prompt gửi cho model. Dùng $ARGUMENTS làm placeholder cho JSON input của hook. Escape bằng backslash để có text literal: \$1.00 hiển thị thành $1.00 |
model | không | Model dùng để đánh giá. Mặc định là một model nhanh |
Tham chiếu script theo đường dẫn
Phần tiêu đề “Tham chiếu script theo đường dẫn”Dùng các placeholder này để tham chiếu script hook tương đối với gốc project hoặc plugin, bất kể thư mục làm việc lúc hook chạy là gì:
${CLAUDE_PROJECT_DIR}: gốc project. Claude Code cũng đặt biến này trong môi trường của MCP server dạng stdio và LSP server của plugin.${CLAUDE_PLUGIN_ROOT}: thư mục cài đặt của plugin, cho script đóng gói cùng một plugin. Thay đổi mỗi lần plugin cập nhật.${CLAUDE_PLUGIN_DATA}: thư mục dữ liệu bền vững của plugin, cho dependency và state cần sống sót qua các lần cập nhật plugin.
Ưu tiên dạng exec cho bất kỳ hook nào tham chiếu placeholder đường dẫn. Dạng exec truyền mỗi phần tử args như một tham số, không cần shell tokenize, nên đường dẫn có khoảng trắng hay ký tự đặc biệt không cần quote. Ở dạng shell, bọc mỗi placeholder trong dấu ngoặc kép.
Ví dụ dùng ${CLAUDE_PROJECT_DIR} để chạy một style checker từ thư mục .claude/hooks/ của project sau mỗi lệnh gọi tool Write hoặc Edit:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh", "args": [] } ] } ] }}Hook của plugin được định nghĩa trong hooks/hooks.json với field description tùy chọn ở cấp cao nhất. Khi một plugin được bật, hooks của nó gộp với hooks của user và project. Ví dụ chạy một script format đóng gói cùng plugin:
{ "description": "Automatic code formatting", "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh", "args": [], "timeout": 30 } ] } ] }}Xem tham chiếu component của plugin để biết chi tiết tạo plugin hooks.
Hooks trong skill và agent
Phần tiêu đề “Hooks trong skill và agent”Ngoài file settings và plugin, hooks có thể được định nghĩa trực tiếp trong skill và subagent qua frontmatter. Các hook này gắn phạm vi với vòng đời của component và chỉ chạy khi component đó đang hoạt động.
Mọi sự kiện hook đều được hỗ trợ. Với subagent, hook Stop tự động được chuyển thành SubagentStop vì đó là sự kiện chạy khi một subagent hoàn tất.
Hooks dùng chung định dạng cấu hình với hooks dựa trên settings nhưng gắn phạm vi với vòng đời của component và được dọn dẹp khi component kết thúc.
Skill sau định nghĩa một PreToolUse hook chạy script kiểm tra bảo mật trước mỗi lệnh Bash:
---name: secure-operationsdescription: Perform operations with security checkshooks: PreToolUse: - matcher: "Bash" hooks: - type: command command: "./scripts/security-check.sh"---Subagent dùng cùng định dạng trong frontmatter YAML của chúng.
Kể từ v2.1.218, frontmatter hooks trong một project subagent chỉ chạy sau khi bạn chấp nhận hộp thoại workspace trust cho thư mục chứa file agent đó. Trước v2.1.218, các hook này có thể chạy từ những thư mục bạn chưa tin cậy.
Menu /hooks
Phần tiêu đề “Menu /hooks”Gõ /hooks trong Claude Code để mở trình duyệt chỉ-xem cho các hook đã cấu hình. Menu hiện mọi sự kiện hook kèm số lượng hook đã cấu hình, cho phép bạn đi sâu vào từng matcher, và hiện chi tiết đầy đủ của mỗi hook handler. Dùng nó để xác minh cấu hình, kiểm tra hook nào tới từ file settings nào, hoặc xem lệnh, prompt, hay URL của một hook.
Menu hiện cả năm loại hook: command, prompt, agent, http, và mcp_tool. Mỗi hook được gắn nhãn tiền tố [type] và nguồn cho biết nó được định nghĩa ở đâu:
User Settings: từ~/.claude/settings.jsonProject Settings: từ.claude/settings.jsonLocal Settings: từ.claude/settings.local.jsonPlugin Hooks: từhooks/hooks.jsoncủa một pluginSession Hooks: đăng ký trong bộ nhớ cho phiên hiện tạiBuilt-in Hooks: đăng ký nội bộ bởi Claude Code
Chọn một hook mở view chi tiết hiện sự kiện, matcher, loại, file nguồn, và toàn bộ lệnh, prompt, hoặc URL. Menu chỉ-xem: muốn thêm, sửa, hay xóa hooks, sửa trực tiếp JSON settings hoặc nhờ Claude thực hiện thay đổi.
Tắt hoặc xóa hooks
Phần tiêu đề “Tắt hoặc xóa hooks”Để xóa một hook, xóa mục của nó trong file settings JSON.
Để tạm tắt toàn bộ hooks mà không xóa chúng, đặt "disableAllHooks": true trong file settings của bạn. Không có cách nào tắt riêng một hook trong khi vẫn giữ nó trong cấu hình.
Setting disableAllHooks tôn trọng hệ thống phân cấp managed settings. Nếu quản trị viên đã cấu hình hooks qua managed policy settings, disableAllHooks đặt ở user, project, hoặc local settings không thể tắt các hooks managed đó. Chỉ disableAllHooks đặt ở tầng managed settings mới tắt được hooks managed.
Chỉnh sửa trực tiếp hooks trong file settings thường được file watcher tự động phát hiện.
Input và output của hook
Phần tiêu đề “Input và output của hook”Command hook nhận dữ liệu JSON qua stdin và báo kết quả qua exit code, stdout, và stderr. HTTP hook nhận cùng JSON đó dưới dạng body của request POST và báo kết quả qua HTTP response body. Phần này bao quát các field và hành vi chung cho mọi sự kiện. Mỗi phần Sự kiện hook bên dưới có input schema riêng và tùy chọn kiểm soát quyết định cho sự kiện đó.
Trên macOS và Linux, command hook chạy trong session riêng không có controlling terminal kể từ v2.1.139. Tiến trình hook và mọi tiến trình con không thể mở /dev/tty hay gửi escape sequence trực tiếp tới giao diện Claude Code. Windows không có /dev/tty. Để hiện thông điệp cho người dùng trên mọi nền tảng, trả về systemMessage trong JSON output. Để kích hoạt thông báo desktop, đặt tiêu đề cửa sổ, hoặc rung chuông, trả về terminalSequence thay vào đó.
Field input chung
Phần tiêu đề “Field input chung”Sự kiện hook nhận các field này dưới dạng JSON, thêm vào các field đặc thù cho sự kiện được ghi trong mỗi phần sự kiện hook. Với command hook, JSON này tới qua stdin; với HTTP hook, nó tới dưới dạng body của request POST.
| Field | Mô tả |
|---|---|
session_id | Định danh phiên hiện tại |
prompt_id | UUID định danh prompt người dùng đang được xử lý. Khớp với attribute prompt.id trên sự kiện OpenTelemetry, để bạn tương quan output hook với telemetry cho một prompt cụ thể. Vắng mặt cho tới lần input đầu tiên của người dùng. Yêu cầu Claude Code v2.1.196 trở lên |
transcript_path | Đường dẫn tới JSON hội thoại. File transcript được ghi bất đồng bộ nên có thể trễ so với hội thoại trong bộ nhớ, nghĩa là có thể chưa gồm tin nhắn gần nhất của lượt hiện tại khi hook chạy. Hooks cần text cuối cùng của assistant trong lượt hiện tại nên dùng last_assistant_message trên Stop và SubagentStop thay vì đọc transcript |
cwd | Thư mục làm việc hiện tại khi hook được gọi |
permission_mode | Chế độ quyền hiện tại: "default", "plan", "acceptEdits", "auto", "dontAsk", hoặc "bypassPermissions". Chế độ nhãn Manual tới dưới dạng "default", không bao giờ là "manual", nên script khớp "default" vẫn hoạt động đúng. Không phải sự kiện nào cũng nhận field này - kiểm tra ví dụ JSON trong mỗi phần sự kiện hook |
effort | Object có field level chứa mức effort đang hoạt động cho lượt này: "low", "medium", "high", "xhigh", hoặc "max". Nếu effort yêu cầu vượt quá mức model hiện tại hỗ trợ, đây là mức đã bị hạ mà model thực sự dùng. Ultracode không phải mức riêng, báo cáo là "xhigh". Có mặt ở các sự kiện xảy ra trong ngữ cảnh dùng tool, như PreToolUse, PostToolUse, Stop, và SubagentStop, khi model hiện tại hỗ trợ tham số effort. Mức này cũng khả dụng cho lệnh hook và tool Bash qua biến môi trường $CLAUDE_EFFORT |
hook_event_name | Tên sự kiện đã xảy ra |
Khi chạy với --agent hoặc bên trong một subagent, có thêm hai field:
| Field | Mô tả |
|---|---|
agent_id | Định danh duy nhất cho subagent. Chỉ có mặt khi hook chạy bên trong một lệnh gọi subagent. Dùng để phân biệt lệnh gọi hook của subagent với lệnh gọi ở luồng chính |
agent_type | Tên agent (ví dụ "Explore" hoặc "security-reviewer"). Có mặt khi phiên dùng --agent hoặc hook chạy bên trong subagent. Với subagent, loại của subagent được ưu tiên hơn giá trị --agent của phiên. Với subagent tùy chỉnh, đây là field name trong frontmatter của agent, không phải tên file. Với subagent do một plugin cung cấp, đây là định danh có phạm vi plugin như my-plugin:reviewer, không phải tên frontmatter trần |
Chỉ hook SessionStart mới có thể nhận field model, và không đảm bảo luôn có mặt. Không có biến môi trường $CLAUDE_MODEL. Một tiến trình hook kế thừa môi trường cha, nên có thể đọc $ANTHROPIC_MODEL nếu bạn đặt nó trong shell, nhưng giá trị đó không đổi khi bạn chuyển model bằng /model trong phiên. Có một nhóm biến không được kế thừa: Claude Code loại bỏ các biến export OTEL_* khỏi mọi subprocess nó spawn, kể cả hooks.
Ví dụ, một PreToolUse hook cho lệnh Bash nhận trên stdin:
{ "session_id": "abc123", "prompt_id": "550e8400-e29b-41d4-a716-446655440000", "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl", "cwd": "/home/user/my-project", "permission_mode": "default", "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "npm test", "description": "Run test suite", "timeout": 120000, "run_in_background": false }, "tool_use_id": "toolu_01ABC123..."}Các field tool_name, tool_input, và tool_use_id là đặc thù cho sự kiện. Mỗi phần sự kiện hook ghi các field bổ sung cho sự kiện đó.
Output theo exit code
Phần tiêu đề “Output theo exit code”Exit code từ lệnh hook của bạn báo cho Claude Code biết hành động nên tiếp tục, bị chặn, hay bị bỏ qua.
Exit 0 nghĩa là thành công. Claude Code phân tích stdout tìm field JSON output. JSON output chỉ được xử lý khi exit 0. Với hầu hết sự kiện, stdout được ghi vào debug log nhưng không hiện trong transcript. Ngoại lệ là UserPromptSubmit, UserPromptExpansion, và SessionStart, nơi stdout được thêm làm context mà Claude có thể thấy và hành động theo.
Exit 2 nghĩa là lỗi chặn (blocking). Claude Code bỏ qua stdout và mọi JSON trong đó. Thay vào đó, text ở stderr được đưa lại cho Claude như một thông điệp lỗi. Hiệu ứng tùy sự kiện: PreToolUse chặn lệnh gọi tool, UserPromptSubmit từ chối prompt, v.v. Xem hành vi exit code 2 theo từng sự kiện để có danh sách đầy đủ.
Một hook exit 2 trong khi in JSON không qua được validation schema của JSON output vẫn chặn: Claude Code dùng stderr làm lý do chặn và ghi lỗi validation vào debug log. Trước v2.1.214, Claude Code coi kết hợp này là lỗi không-chặn và hành động vẫn tiếp tục.
Bất kỳ exit code nào khác là lỗi không-chặn với hầu hết sự kiện hook. Transcript hiện thông báo <hook name> hook error kèm dòng đầu của stderr, để bạn nhận diện nguyên nhân mà không cần --debug. Thực thi tiếp tục và toàn bộ stderr được ghi vào debug log.
Ví dụ, một script hook chặn lệnh Bash nguy hiểm:
#!/bin/bash# Đọc JSON input từ stdin, kiểm tra lệnhinput=$(cat)command=$(jq -r '.tool_input.command' <<<"$input")
if [[ "$command" == rm* ]]; then echo "Blocked: rm commands are not allowed" >&2 exit 2 # Lỗi chặn: lệnh gọi tool bị ngănfi
exit 0 # Không có quyết định: luồng permission bình thường áp dụngHành vi exit code 2 theo từng sự kiện
Phần tiêu đề “Hành vi exit code 2 theo từng sự kiện”Exit code 2 là cách một hook báo “dừng lại, đừng làm điều này”. Hiệu ứng tùy sự kiện, vì một số sự kiện đại diện cho hành động có thể chặn (như một lệnh gọi tool chưa xảy ra), số khác đại diện cho điều đã xảy ra rồi hoặc không thể ngăn.
| Sự kiện hook | Chặn được? | Điều gì xảy ra khi exit 2 |
|---|---|---|
PreToolUse | Có | Chặn lệnh gọi tool |
PermissionRequest | Có | Từ chối quyền |
UserPromptSubmit | Có | Chặn xử lý prompt và xóa prompt |
UserPromptExpansion | Có | Chặn việc mở rộng |
Stop | Có | Ngăn Claude dừng, tiếp tục hội thoại |
SubagentStop | Có | Ngăn subagent dừng |
TeammateIdle | Có | Ngăn teammate chuyển sang idle, để nó tiếp tục làm việc |
TaskCreated | Có | Rollback việc tạo task |
TaskCompleted | Có | Ngăn task được đánh dấu hoàn thành |
ConfigChange | Có | Chặn thay đổi cấu hình có hiệu lực (trừ policy_settings) |
StopFailure | Không | Output và exit code bị bỏ qua |
PostToolUse | Không | Hiện stderr cho Claude; tool đã chạy rồi |
PostToolUseFailure | Không | Hiện stderr cho Claude; tool đã thất bại rồi |
PostToolBatch | Có | Dừng vòng lặp agentic trước lệnh gọi model tiếp theo |
PermissionDenied | Không | Exit code và stderr bị bỏ qua vì việc từ chối đã xảy ra rồi. Dùng JSON hookSpecificOutput.retry: true để báo model có thể thử lại |
Notification | Không | Hiện stderr chỉ cho người dùng |
SubagentStart | Không | Hiện stderr chỉ cho người dùng |
SessionStart | Không | Hiện stderr chỉ cho người dùng |
Setup | Không | Hiện stderr chỉ cho người dùng |
SessionEnd | Không | Hiện stderr chỉ cho người dùng |
CwdChanged | Không | Hiện stderr chỉ cho người dùng |
FileChanged | Không | Hiện stderr chỉ cho người dùng |
PreCompact | Có | Chặn việc nén |
PostCompact | Không | Hiện stderr chỉ cho người dùng |
Elicitation | Có | Từ chối elicitation |
ElicitationResult | Có | Chặn phản hồi (hành động trở thành decline) |
WorktreeCreate | Có | Bất kỳ exit code khác 0 nào cũng khiến việc tạo worktree thất bại |
WorktreeRemove | Không | Lỗi chỉ được ghi log ở chế độ debug |
InstructionsLoaded | Không | Exit code bị bỏ qua |
MessageDisplay | Không | Text gốc vẫn được hiển thị |
Với SessionStart, Setup, và SubagentStart, stderr của exit code 2 hiện trong transcript như một thông báo <hook name> hook error, giống lỗi không-chặn. Claude không thấy nó, và phiên hoặc subagent vẫn tiếp tục. Với SubagentStart, thông báo hiện trong transcript riêng của subagent, không phải hội thoại cha.
Kể từ Claude Code v2.1.199, SessionStart, Setup, và SubagentStart hiện stderr của exit code 2 trong transcript. Phiên bản trước đó chỉ ghi vào debug log.
Xử lý response HTTP
Phần tiêu đề “Xử lý response HTTP”HTTP hook dùng mã trạng thái HTTP và response body thay vì exit code và stdout:
- 2xx với body rỗng: thành công, tương đương exit code 0 không có output
- 2xx với body text thuần: thành công, text được thêm làm context
- 2xx với body JSON: thành công, được phân tích dùng cùng schema JSON output như command hook
- Status không phải 2xx: lỗi không-chặn, thực thi tiếp tục
- Lỗi kết nối hoặc timeout: lỗi không-chặn, thực thi tiếp tục
Khác với command hook, HTTP hook không thể báo lỗi chặn chỉ qua mã trạng thái. Để chặn một lệnh gọi tool hay từ chối một quyền, trả về response 2xx với body JSON chứa field quyết định phù hợp.
JSON output
Phần tiêu đề “JSON output”Exit code chỉ cho phép chặn hoặc im lặng, nhưng JSON output cho kiểm soát chi tiết hơn. Thay vì exit code 2 để chặn, exit 0 và in một object JSON ra stdout. Claude Code đọc các field cụ thể từ JSON đó để kiểm soát hành vi, gồm cả kiểm soát quyết định để chặn, cho phép, hoặc leo thang cho người dùng quyết định.
Stdout của hook chỉ được chứa object JSON. Nếu shell profile của bạn in text lúc khởi động, nó có thể gây nhiễu việc phân tích JSON.
Các chuỗi output của hook, gồm additionalContext, systemMessage, và stdout thuần, bị giới hạn ở 10.000 ký tự. Output vượt giới hạn này được lưu vào file và thay bằng bản xem trước cùng đường dẫn file, giống cách kết quả tool lớn được xử lý.
Object JSON hỗ trợ ba loại field:
- Field phổ quát như
continuehoạt động trên mọi sự kiện. Liệt kê trong bảng dưới. decisionvàreasonở cấp cao nhất được một số sự kiện dùng để chặn hoặc phản hồi.hookSpecificOutputlà object lồng cho các sự kiện cần kiểm soát chi tiết hơn. Yêu cầu fieldhookEventNameđặt bằng tên sự kiện.
| Field | Mặc định | Mô tả |
|---|---|---|
continue | true | Nếu false, Claude dừng xử lý hoàn toàn sau khi hook chạy. Ưu tiên hơn mọi field quyết định đặc thù sự kiện khác |
stopReason | không có | Thông điệp hiện cho người dùng khi continue là false. Không hiện cho Claude |
suppressOutput | false | Nếu true, ẩn stdout của hook khỏi transcript. Stdout vẫn hiện trong debug log |
systemMessage | không có | Thông điệp cảnh báo hiện cho người dùng |
terminalSequence | không có | Một chuỗi escape terminal để Claude Code phát thay bạn, như thông báo desktop, tiêu đề cửa sổ, hay chuông. Giới hạn ở OSC 0/1/2/9/99/777 và BEL. Nếu giá trị chứa gì đó ngoài allowlist, field bị bỏ qua. Dùng cái này thay vì ghi vào /dev/tty, vốn không khả dụng cho hooks |
Để dừng Claude hoàn toàn bất kể loại sự kiện:
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }Với hook PreToolUse và PostToolUse, việc dừng áp dụng kể cả khi lệnh gọi tool thất bại hoặc hoàn tất trong lúc Claude vẫn đang stream phản hồi.
Gửi thông báo terminal
Phần tiêu đề “Gửi thông báo terminal”Field terminalSequence yêu cầu Claude Code v2.1.141 trở lên.
Hooks chạy không có controlling terminal, nên ghi escape sequence trực tiếp vào /dev/tty sẽ thất bại. Thay vào đó, trả về escape sequence trong field terminalSequence và Claude Code phát nó thay bạn qua đường ghi terminal riêng của nó. Cách này không có race condition, hoạt động trong tmux và GNU screen, và hoạt động trên Windows nơi không có /dev/tty.
Field này nhận một chuỗi gồm một hoặc nhiều escape sequence trong allowlist:
- OSC
0,1,2: tiêu đề cửa sổ và icon - OSC
9: thông báo của iTerm2, ConEmu, Windows Terminal, và WezTerm, gồm cả9;4cho tiến độ trên taskbar - OSC
99: thông báo của Kitty - OSC
777: thông báo của urxvt, Ghostty, và Warp - BEL trần
Sequence có thể kết thúc bằng BEL hoặc ST. Bất cứ gì ngoài allowlist, gồm sequence con trỏ và màu CSI, sequence bảng màu OSC, hyperlink OSC 8, ghi clipboard OSC 52, và OSC 1337, đều bị từ chối và field bị bỏ qua.
Ví dụ dưới đây kích hoạt thông báo desktop từ một Notification hook. Escape sequence được dựng bằng octal escape của printf để các byte điều khiển không bao giờ xuất hiện trên dòng lệnh shell, và jq -n --arg dựng JSON output để dấu ngoặc kép, backslash, và newline trong thông điệp thông báo được escape đúng:
#!/bin/bash# Notification hook: ping desktop khi Claude Code cần chú ý.input=$(cat)title="Claude Code"body=$(jq -r '.message // "Needs your attention"' <<<"$input")seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")jq -nc --arg seq "$seq" '{terminalSequence: $seq}'Dạng { "terminalSequence": "..." } giống nhau từ bất kỳ shell hay ngôn ngữ nào. Trên Windows, dựng chuỗi escape trong PowerShell hoặc một script rồi phát cùng object JSON đó.
Thêm context cho Claude
Phần tiêu đề “Thêm context cho Claude”Field additionalContext truyền một chuỗi từ hook của bạn vào context window của Claude. Claude Code bọc chuỗi trong một system reminder và chèn nó vào hội thoại tại điểm hook chạy. Claude đọc reminder đó ở request model tiếp theo, nhưng nó không hiện như một tin nhắn chat trong giao diện.
Trả additionalContext bên trong hookSpecificOutput cùng với tên sự kiện:
{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead." }}Nơi reminder xuất hiện tùy sự kiện:
- SessionStart, Setup, và SubagentStart: ở đầu hội thoại, trước prompt đầu tiên
- UserPromptSubmit và UserPromptExpansion: cùng với prompt đã gửi
- PreToolUse, PostToolUse, PostToolUseFailure, và PostToolBatch: cạnh kết quả tool
- Stop và SubagentStop: ở cuối lượt. Hội thoại tiếp tục để Claude có thể hành động theo phản hồi. Xem kiểm soát quyết định Stop
Khi nhiều hook cùng trả additionalContext cho cùng một sự kiện, Claude nhận tất cả các giá trị. Nếu một giá trị vượt quá 10.000 ký tự, Claude Code ghi toàn bộ text vào một file trong thư mục phiên và cho Claude đường dẫn file kèm bản xem trước ngắn.
Dùng additionalContext cho thông tin Claude nên biết về trạng thái hiện tại của môi trường hoặc thao tác vừa chạy:
- Trạng thái môi trường: branch hiện tại, deployment target, hay feature flag đang bật
- Quy tắc project có điều kiện: lệnh test nào áp dụng cho file vừa sửa, thư mục nào chỉ-đọc trong worktree này
- Dữ liệu bên ngoài: issue đang mở gán cho bạn, kết quả CI gần đây, nội dung lấy từ một dịch vụ nội bộ
Với hướng dẫn không bao giờ đổi, ưu tiên CLAUDE.md - nó nạp mà không cần chạy script và là nơi chuẩn cho quy ước project tĩnh.
Viết text dưới dạng phát biểu sự thật thay vì chỉ thị mệnh lệnh dạng hệ thống. Cách diễn đạt như “Deployment target hiện là production” hay “Repo này dùng bun test” đọc như thông tin project. Text được đóng khung như lệnh hệ thống ngoài luồng có thể kích hoạt cơ chế phòng vệ chống prompt-injection của Claude, khiến Claude hiện text đó cho bạn thay vì coi nó là context.
Claude Code lưu text đã tiêm vào transcript phiên. Với sự kiện giữa phiên như PostToolUse hay UserPromptSubmit, khi bạn resume bằng --continue hoặc --resume, Claude Code phát lại text đã lưu thay vì chạy lại hook cho các lượt trước, nên giá trị như timestamp hay commit SHA có thể trở nên cũ. Hooks SessionStart chạy lại khi resume với source đặt là "resume", hoặc "fork" nếu bạn thêm --fork-session, để chúng có thể làm mới context.
Kiểm soát quyết định
Phần tiêu đề “Kiểm soát quyết định”Không phải mọi sự kiện đều hỗ trợ chặn hay kiểm soát hành vi qua JSON. Các sự kiện có hỗ trợ dùng bộ field khác nhau để diễn đạt quyết định đó. Dùng bảng này để tra cứu nhanh trước khi viết hook:
| Sự kiện | Kiểu quyết định | Field chính |
|---|---|---|
| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | decision cấp cao nhất | decision: "block", reason. Stop và SubagentStop cũng nhận hookSpecificOutput.additionalContext cho phản hồi không-lỗi tiếp tục hội thoại |
| TeammateIdle, TaskCreated, TaskCompleted | Exit code hoặc continue: false | Exit code 2 chặn hành động kèm phản hồi qua stderr. JSON {"continue": false, "stopReason": "..."} cũng dừng teammate hoàn toàn, giống hành vi hook Stop |
| PreToolUse | hookSpecificOutput | permissionDecision (allow/deny/ask/defer), permissionDecisionReason |
| PermissionRequest | hookSpecificOutput | decision.behavior (allow/deny) |
| PermissionDenied | hookSpecificOutput | retry: true báo model có thể thử lại lệnh gọi tool bị từ chối |
| WorktreeCreate | trả về đường dẫn | Command hook in đường dẫn ra stdout; HTTP hook trả hookSpecificOutput.worktreePath. Hook thất bại hoặc thiếu đường dẫn khiến việc tạo thất bại |
| Elicitation | hookSpecificOutput | action (accept/decline/cancel), content (giá trị field form cho accept) |
| ElicitationResult | hookSpecificOutput | action (accept/decline/cancel), content (ghi đè giá trị field form) |
| MessageDisplay | hookSpecificOutput | displayContent thay text hiển thị trên màn hình. Chỉ ảnh hưởng hiển thị: transcript và những gì Claude thấy giữ nguyên bản gốc |
| SessionStart, Setup, SubagentStart | Chỉ context | hookSpecificOutput.additionalContext thêm context cho Claude. SessionStart còn nhận initialUserMessage, watchPaths, sessionTitle, và reloadSkills. Không có kiểm soát chặn hay quyết định |
| WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | Không có | Không có kiểm soát quyết định. Dùng cho side effect như ghi log hay dọn dẹp |
Một vài sự kiện còn có thể viết lại nội dung thay vì chỉ cho phép hoặc chặn:
PreToolUse:updatedInputtrực tiếp dướihookSpecificOutputthay tham số của tool trước khi nó chạy. Xem kiểm soát quyết định PreToolUsePermissionRequest:updatedInputbên trong objectdecision. Xem kiểm soát quyết định PermissionRequestPostToolUse:updatedToolOutputthay kết quả của tool. Xem kiểm soát quyết định PostToolUseUserPromptSubmit: không thể thay prompt; chỉ tiêmadditionalContextcùng với nó
Với nhu cầu redact hay biến đổi dữ liệu, can thiệp ở PreToolUse cho input gửi ra của tool và PostToolUse cho kết quả trả về của tool.
Dưới đây là ví dụ cho từng kiểu:
decision cấp cao nhất - dùng bởi UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, và PreCompact. Giá trị duy nhất là "block". Để cho phép hành động tiếp tục, bỏ decision khỏi JSON, hoặc exit 0 mà không có JSON nào cả:
{ "decision": "block", "reason": "Test suite must pass before proceeding"}PreToolUse - dùng hookSpecificOutput để kiểm soát chi tiết hơn: allow, deny, hoặc leo thang cho người dùng. Bạn cũng có thể sửa tool input trước khi nó chạy hoặc tiêm thêm context cho Claude:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Database writes are not allowed" }}PermissionRequest - dùng hookSpecificOutput để cho phép hoặc từ chối một yêu cầu quyền thay mặt người dùng. Khi cho phép, bạn cũng có thể sửa input của tool hoặc áp quy tắc quyền để người dùng không bị hỏi lại:
{ "hookSpecificOutput": { "hookEventName": "PermissionRequest", "decision": { "behavior": "allow", "updatedInput": { "command": "npm run lint" } } }}Xem thêm ví dụ mở rộng gồm validate lệnh Bash, lọc prompt, và script tự động phê duyệt trong hướng dẫn Hooks và ví dụ tham chiếu Bash command validator trên GitHub.
Sự kiện hook
Phần tiêu đề “Sự kiện hook”Mỗi sự kiện tương ứng với một điểm trong vòng đời Claude Code nơi hooks có thể chạy. Các phần bên dưới được sắp theo thứ tự vòng đời: từ lúc thiết lập phiên qua vòng lặp agentic tới lúc phiên kết thúc. Mỗi phần mô tả thời điểm sự kiện chạy, matcher nó hỗ trợ, JSON input nó nhận, và cách kiểm soát hành vi qua output.
SessionStart
Phần tiêu đề “SessionStart”Chạy khi Claude Code bắt đầu một phiên mới hoặc resume một phiên có sẵn. Hữu ích để nạp ngữ cảnh phát triển như issue đang mở hay thay đổi gần đây trong codebase, hoặc thiết lập biến môi trường. Với ngữ cảnh tĩnh không cần script, dùng CLAUDE.md thay vào đó.
SessionStart chạy ở mọi phiên, nên giữ hooks này nhanh. Chỉ type: "command" và type: "mcp_tool" được hỗ trợ.
Giá trị matcher tương ứng với cách phiên được khởi tạo:
| Matcher | Thời điểm chạy |
|---|---|
startup | Phiên mới |
resume | --resume, --continue, hoặc /resume |
clear | /clear |
compact | Nén tự động hoặc thủ công |
fork | Một phiên mới được fork từ phiên có sẵn: --fork-session kèm --resume/--continue, bản copy background của /fork, hoặc /branch |
Trước v2.1.214, các phiên fork báo source là "resume".
SessionStart input
Phần tiêu đề “SessionStart input”Ngoài field input chung, hook SessionStart nhận source và tùy chọn model, agent_type, và session_title:
| Field | Mô tả |
|---|---|
source | Cách phiên bắt đầu: "startup" cho phiên mới, "resume" cho phiên resume, "clear" sau /clear, "compact" sau khi nén, hoặc "fork" cho phiên mới fork từ phiên có sẵn |
model | Định danh model đang hoạt động. Có thể vắng mặt, ví dụ sau /clear hoặc khi phiên được khôi phục qua conversation recovery, nên kiểm tra field này trước khi đọc |
agent_type | Tên agent, có mặt khi bạn khởi động Claude Code với claude --agent <name> |
session_title | Tiêu đề phiên hiện tại nếu đã được đặt, ví dụ qua --name hoặc /rename. Một hook phát ra sessionTitle có thể kiểm tra session_title trước để tránh ghi đè tiêu đề người dùng đã tự đặt |
{ "session_id": "abc123", "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", "cwd": "/Users/...", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-sonnet-5"}Kiểm soát quyết định SessionStart
Phần tiêu đề “Kiểm soát quyết định SessionStart”Mọi text script hook của bạn in ra stdout được thêm làm context cho Claude. Ngoài các field JSON output khả dụng cho mọi hook, bạn có thể trả về các field đặc thù sự kiện sau:
| Field | Mô tả |
|---|---|
additionalContext | Chuỗi thêm vào context của Claude ở đầu hội thoại, trước prompt đầu tiên. Xem Thêm context cho Claude |
initialUserMessage | Chuỗi dùng làm tin nhắn người dùng đầu tiên của phiên. Áp dụng ở chế độ không tương tác với flag -p, nơi nó trở thành lượt đầu tiên kể cả khi không có prompt nào được cung cấp. Nếu có prompt, nó theo sau như lượt kế tiếp. Khác với additionalContext (gắn vào một lượt có sẵn), field này tạo ra lượt đó |
sessionTitle | Đặt tiêu đề phiên, hiệu ứng giống /rename. Dùng để tự động đặt tên phiên theo thư mục khởi chạy, git branch, hay tên worktree. Áp dụng khi source là "startup", "resume", hoặc "fork"; bị bỏ qua ở "clear" và "compact" |
watchPaths | Mảng đường dẫn tuyệt đối cần theo dõi cho sự kiện FileChanged trong phiên này |
reloadSkills | Boolean. Khi true, Claude Code quét lại thư mục skill và command sau khi các hook SessionStart hoàn tất, để skill mà hook vừa cài có thể dùng được ngay trong cùng phiên, từ prompt đầu tiên |
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2", "sessionTitle": "auth-refactor" }}Vì stdout thuần đã tới được Claude ở sự kiện này, một hook chỉ nạp context có thể in trực tiếp ra stdout không cần dựng JSON. Dùng dạng JSON khi bạn cần kết hợp context với các field khác như suppressOutput hay sessionTitle.
Dùng reloadSkills khi một hook SessionStart cài đặt hoặc cập nhật skill. Việc phát hiện skill thường chạy trước khi hooks SessionStart hoàn tất, nên các file hook ghi vào ~/.claude/skills/ hay .claude/skills/ sẽ chỉ xuất hiện ở phiên kế tiếp nếu không có reloadSkills.
Lưu lại biến môi trường
Phần tiêu đề “Lưu lại biến môi trường”Hooks SessionStart có quyền truy cập biến môi trường CLAUDE_ENV_FILE, cung cấp đường dẫn file nơi bạn có thể lưu biến môi trường cho các lệnh Bash tiếp theo.
Để đặt từng biến môi trường, ghi các câu lệnh export vào CLAUDE_ENV_FILE. Dùng append (>>) để giữ lại biến do hook khác đặt:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE" echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE" echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"fi
exit 0Để bắt toàn bộ thay đổi môi trường từ các lệnh setup, so sánh biến đã export trước và sau:
#!/bin/bash
ENV_BEFORE=$(export -p | sort)
# Chạy các lệnh setup làm thay đổi môi trườngsource ~/.nvm/nvm.shnvm use 20
if [ -n "$CLAUDE_ENV_FILE" ]; then ENV_AFTER=$(export -p | sort) comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"fi
exit 0Mọi biến ghi vào file này sẽ khả dụng trong toàn bộ lệnh Bash tiếp theo mà Claude Code chạy trong phiên.
Setup
Phần tiêu đề “Setup”Chỉ chạy khi bạn khởi động Claude Code với --init-only, hoặc với --init/--maintenance ở chế độ không tương tác với flag -p. Không chạy khi khởi động bình thường. Dùng cho cài đặt dependency một lần hoặc dọn dẹp theo lịch mà bạn kích hoạt tường minh từ CI hoặc script, tách biệt với khởi động phiên thông thường. Với khởi tạo mỗi phiên, dùng SessionStart thay vào đó.
Giá trị matcher tương ứng với flag CLI đã kích hoạt hook:
| Matcher | Thời điểm chạy |
|---|---|
init | claude --init-only hoặc claude -p --init |
maintenance | claude -p --maintenance |
Khi bạn chạy claude --init-only, Claude Code chạy hooks Setup và hooks SessionStart với matcher startup, rồi thoát mà không bắt đầu hội thoại.
--init và --maintenance chỉ kích hoạt hooks Setup khi kết hợp với -p. Trong phiên tương tác, hai flag này hiện chưa kích hoạt hooks Setup.
Khi bạn bắt đầu hoặc tiếp tục hội thoại với -p, bạn cũng cần cung cấp một prompt, dưới dạng tham số hoặc pipe qua stdin. Bạn có thể bỏ qua prompt khi hook SessionStart cung cấp initialUserMessage hoặc khi resume một phiên bằng lệnh gọi tool bị hoãn.
Vì Setup không chạy ở mọi lần khởi chạy, một plugin cần một dependency được cài không thể chỉ dựa vào Setup. Cách thực tế là kiểm tra dependency ở lần dùng đầu tiên và cài nếu thiếu, ví dụ một hook hay skill kiểm tra ${CLAUDE_PLUGIN_DATA}/node_modules và chạy npm install nếu vắng mặt.
Setup input
Phần tiêu đề “Setup input”Ngoài field input chung, hooks Setup nhận field trigger là "init" hoặc "maintenance":
{ "session_id": "abc123", "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", "cwd": "/Users/...", "hook_event_name": "Setup", "trigger": "init"}Kiểm soát quyết định Setup
Phần tiêu đề “Kiểm soát quyết định Setup”Hooks Setup không thể chặn. Bất kỳ exit code khác 0 nào, kể cả 2, đều hiện stderr cho người dùng dưới dạng thông báo <hook name> hook error, và thực thi tiếp tục. Ở chế độ không tương tác, output của hook chỉ hiện khi bạn khởi chạy với --verbose.
Để truyền thông tin vào context của Claude, trả additionalContext trong JSON output; stdout thuần chỉ được ghi vào debug log. Ngoài các field JSON output khả dụng cho mọi hook, bạn có thể trả các field đặc thù sự kiện sau:
| Field | Mô tả |
|---|---|
additionalContext | Chuỗi thêm vào context của Claude. Giá trị từ nhiều hook được nối lại |
{ "hookSpecificOutput": { "hookEventName": "Setup", "additionalContext": "Dependencies installed: node_modules, .venv" }}Hooks Setup có quyền truy cập CLAUDE_ENV_FILE. Biến ghi vào file đó được giữ lại cho các lệnh Bash tiếp theo trong phiên, giống hooks SessionStart. Chỉ type: "command" và type: "mcp_tool" được hỗ trợ.
InstructionsLoaded
Phần tiêu đề “InstructionsLoaded”Chạy khi một file CLAUDE.md hay .claude/rules/*.md được nạp vào context. Sự kiện này chạy khi phiên bắt đầu cho các file được nạp sẵn (eager), và chạy lại sau khi file được nạp muộn, ví dụ khi Claude truy cập một thư mục con chứa CLAUDE.md lồng nhau, hoặc khi rule có điều kiện với frontmatter paths: khớp. Hook không hỗ trợ chặn hay kiểm soát quyết định. Nó chạy bất đồng bộ chỉ để quan sát (observability).
Matcher chạy trên load_reason. Ví dụ, dùng "matcher": "session_start" để chỉ chạy cho file nạp lúc phiên bắt đầu, hoặc "matcher": "path_glob_match|nested_traversal" để chỉ chạy cho các lần nạp muộn.
InstructionsLoaded input
Phần tiêu đề “InstructionsLoaded input”Ngoài field input chung, hooks InstructionsLoaded nhận các field sau:
| Field | Mô tả |
|---|---|
file_path | Đường dẫn tuyệt đối tới file hướng dẫn vừa được nạp |
memory_type | Phạm vi của file: "User", "Project", "Local", hoặc "Managed" |
load_reason | Lý do file được nạp: "session_start", "nested_traversal", "path_glob_match", "include", hoặc "compact". Giá trị "compact" xảy ra khi file hướng dẫn được nạp lại sau một lần nén |
globs | Mẫu glob đường dẫn từ frontmatter paths: của file, nếu có. Chỉ có mặt với lần nạp path_glob_match |
trigger_file_path | Đường dẫn tới file mà việc truy cập nó kích hoạt lần nạp này, cho lần nạp muộn |
parent_file_path | Đường dẫn tới file hướng dẫn cha đã include file này, cho lần nạp include |
{ "session_id": "abc123", "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl", "cwd": "/Users/my-project", "hook_event_name": "InstructionsLoaded", "file_path": "/Users/my-project/CLAUDE.md", "memory_type": "Project", "load_reason": "session_start"}Kiểm soát quyết định InstructionsLoaded
Phần tiêu đề “Kiểm soát quyết định InstructionsLoaded”Hooks InstructionsLoaded không có kiểm soát quyết định. Chúng không thể chặn hay sửa việc nạp hướng dẫn. Dùng sự kiện này để ghi log audit, theo dõi tuân thủ, hoặc quan sát hệ thống.
UserPromptSubmit
Phần tiêu đề “UserPromptSubmit”Chạy khi người dùng gửi một prompt, trước khi Claude xử lý. Cho phép bạn thêm context dựa trên prompt/hội thoại, validate prompt, hoặc chặn một số loại prompt.
Hooks UserPromptSubmit có timeout mặc định 30 giây cho các loại command, http, và mcp_tool, ngắn hơn mặc định 600 giây ở hầu hết sự kiện khác. Vì hook này chạy trước mọi prompt và chặn việc xử lý model cho tới khi hoàn tất, một hook bị treo sẽ làm nghẽn cả phiên. Nếu hook của bạn cần nhiều thời gian hơn, đặt field timeout trong mục hook.
Một command, HTTP, hoặc MCP tool hook của UserPromptSubmit chạm timeout sẽ bị hủy và output của nó, gồm cả additionalContext, bị bỏ. Prompt vẫn tới Claude nhưng không có context đó. Kể từ v2.1.196, transcript hiện thông báo nêu tên hook, timeout đã xảy ra, và việc output bị bỏ. Phiên bản trước không có thông báo.
UserPromptSubmit input
Phần tiêu đề “UserPromptSubmit input”Ngoài field input chung, hooks UserPromptSubmit nhận field prompt chứa text người dùng đã gửi.
{ "session_id": "abc123", "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", "cwd": "/Users/...", "permission_mode": "default", "hook_event_name": "UserPromptSubmit", "prompt": "Write a function to calculate the factorial of a number"}Kiểm soát quyết định UserPromptSubmit
Phần tiêu đề “Kiểm soát quyết định UserPromptSubmit”Hooks UserPromptSubmit có thể kiểm soát việc một prompt có được xử lý không và thêm context. Mọi field JSON output đều khả dụng.
Có hai cách thêm context vào hội thoại khi exit code 0:
- Stdout text thuần: mọi text không phải JSON in ra stdout được thêm làm context
- JSON với
additionalContext: dùng định dạng JSON dưới đây để kiểm soát chi tiết hơn. Giá trịadditionalContextđược thêm làm context
Stdout thuần hiện như output hook trong transcript. Giá trị additionalContext được tiêm dưới dạng system reminder mà Claude đọc không có mục transcript hiển thị.
Để chặn một prompt, trả về object JSON với decision là "block":
| Field | Mô tả |
|---|---|
decision | "block" ngăn prompt được xử lý và xóa nó khỏi context. Bỏ qua để cho prompt tiếp tục |
reason | Hiện cho người dùng khi decision là "block". Không thêm vào context |
additionalContext | Chuỗi thêm vào context của Claude cùng với prompt đã gửi. Xem Thêm context cho Claude |
sessionTitle | Đặt tiêu đề phiên. Dùng để tự động đặt tên phiên theo nội dung prompt |
suppressOriginalPrompt | Nếu true khi decision là "block", bỏ text prompt gốc khỏi thông điệp chặn hiện cho người dùng |
{ "decision": "block", "reason": "Explanation for decision", "hookSpecificOutput": { "hookEventName": "UserPromptSubmit", "additionalContext": "My additional context here", "sessionTitle": "My session title" }}UserPromptExpansion
Phần tiêu đề “UserPromptExpansion”Chạy khi một lệnh người dùng gõ mở rộng thành prompt trước khi tới Claude. Dùng để chặn một lệnh cụ thể khỏi bị gọi trực tiếp, tiêm context cho một skill nào đó, hoặc ghi log lệnh nào người dùng gọi. Ví dụ, hook khớp deploy có thể chặn /deploy trừ khi có file phê duyệt, hoặc hook khớp một skill review có thể thêm checklist review của team làm additionalContext.
Sự kiện này bao phủ đường mà PreToolUse không phủ: hook PreToolUse khớp tool Skill chỉ chạy khi Claude gọi tool, nhưng gõ /skillname trực tiếp lại bỏ qua PreToolUse. UserPromptExpansion chạy trên đường trực tiếp đó.
Khớp trên command_name. Để trống matcher để chạy trên mọi lệnh dạng prompt.
UserPromptExpansion input
Phần tiêu đề “UserPromptExpansion input”Ngoài field input chung, hooks UserPromptExpansion nhận expansion_type, command_name, command_args, command_source, và chuỗi prompt gốc. Field expansion_type là slash_command cho skill và custom command, hoặc mcp_prompt cho prompt từ MCP server.
{ "session_id": "abc123", "transcript_path": "/Users/.../00893aaf.jsonl", "cwd": "/Users/...", "permission_mode": "default", "hook_event_name": "UserPromptExpansion", "expansion_type": "slash_command", "command_name": "example-skill", "command_args": "arg1 arg2", "command_source": "plugin", "prompt": "/example-skill arg1 arg2"}Kiểm soát quyết định UserPromptExpansion
Phần tiêu đề “Kiểm soát quyết định UserPromptExpansion”Hooks UserPromptExpansion có thể chặn việc mở rộng hoặc thêm context. Mọi field JSON output đều khả dụng.
| Field | Mô tả |
|---|---|
decision | "block" ngăn lệnh mở rộng. Bỏ qua để cho phép nó tiếp tục |
reason | Hiện cho người dùng khi decision là "block" |
additionalContext | Chuỗi thêm vào context của Claude cùng với prompt đã mở rộng. Xem Thêm context cho Claude |
{ "decision": "block", "reason": "This slash command is not available", "hookSpecificOutput": { "hookEventName": "UserPromptExpansion", "additionalContext": "Additional context for this expansion" }}MessageDisplay
Phần tiêu đề “MessageDisplay”Chạy trong lúc một tin nhắn assistant đang stream ra màn hình. Claude Code hiển thị tin nhắn theo từng đợt: mỗi khi một batch dòng vừa hoàn tất sẵn sàng render, hook chạy một lần với các dòng đó và Claude Code render text thay thế của hook vào chỗ đó. Một tin nhắn dài tạo ra nhiều lần gọi; một tin nhắn ngắn có thể chỉ tạo một lần.
Dùng MessageDisplay để:
- loại bỏ markdown cho hiển thị tối giản
- biến đổi text mà một ứng dụng Agent SDK hiện cho người dùng của nó
- redact API key hay hostname nội bộ khỏi phản hồi của Claude
Claude Code giữ mỗi batch cho tới khi hook của bạn trả về, nên giữ hook nhanh. Nếu hook thất bại hoặc hết giờ, Claude Code hiện text gốc. Timeout mặc định của sự kiện này là 10 giây; nếu hook cần nhiều thời gian hơn, đặt field timeout trong mục hook.
MessageDisplay chỉ ảnh hưởng hiển thị: text thay thế chỉ đổi những gì render trên màn hình. Transcript và những gì Claude thấy vẫn giữ text gốc, nên Claude không bao giờ thấy bản thay thế, và chế độ verbose hiện bản gốc. Hook chỉ nhận text tin nhắn assistant, nên kết quả tool và text bạn gõ vẫn render không đổi.
MessageDisplay không hỗ trợ matcher và chạy cho mọi tin nhắn assistant có stream text; tin nhắn không có text, như phản hồi chỉ gọi tool, không kích hoạt nó.
Ở lần chạy không tương tác, gồm cả Agent SDK query và claude -p, MessageDisplay chạy một lần mỗi tin nhắn assistant thay vì mỗi batch dòng. Lệnh gọi duy nhất đó tới sau khi tin nhắn hoàn tất và mang toàn bộ text tin nhắn: index là 0, final là true, và delta chứa toàn bộ tin nhắn.
MessageDisplay input
Phần tiêu đề “MessageDisplay input”Ngoài field input chung, hooks MessageDisplay nhận định danh cho lượt và tin nhắn, vị trí của lần gọi này trong tin nhắn, và text mới trong delta.
| Field | Mô tả |
|---|---|
turn_id | UUID của lượt hiện tại |
message_id | UUID của tin nhắn assistant đang hiển thị. Ổn định qua mọi batch của cùng tin nhắn. Đây không phải id msg_… của API, nên không thể tương quan với id tin nhắn trong transcript |
index | Chỉ số bắt đầu từ 0 của batch này trong tin nhắn |
final | true ở batch cuối cùng của tin nhắn. Mỗi tin nhắn có đúng một batch cuối |
delta | Các dòng vừa hoàn tất kể từ batch trước, kèm ký tự xuống dòng kết thúc. Luôn là dòng trọn vẹn, trừ batch cuối có thể kết thúc giữa dòng. Ở lần chạy tương tác, delta của batch cuối rỗng khi tin nhắn kết thúc bằng xuống dòng, nên dùng final, không phải delta khác rỗng, làm tín hiệu kết thúc tin nhắn. Ở Agent SDK và claude -p, lệnh gọi duy nhất mang toàn bộ tin nhắn |
{ "session_id": "abc123", "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl", "cwd": "/Users/my-project", "hook_event_name": "MessageDisplay", "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10", "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b", "index": 0, "final": false, "delta": "Here is the plan:\n"}MessageDisplay output
Phần tiêu đề “MessageDisplay output”Ngoài các field JSON output khả dụng cho mọi hook, hooks MessageDisplay có thể trả displayContent để thay delta trên màn hình:
| Field | Mô tả |
|---|---|
displayContent | Text hiển thị thay cho delta. Bỏ qua để hiện bản gốc |
MessageDisplay không có kiểm soát quyết định. Không thể chặn tin nhắn hay đổi những gì lưu trong transcript hoặc gửi cho Claude.
Ví dụ dưới đây loại bỏ định dạng markdown khỏi phản hồi của Claude để hiển thị dạng text thuần. Script đọc mỗi batch từ stdin, xóa dấu bold và backtick code inline khỏi delta, và trả kết quả dưới dạng displayContent.
Đăng ký command hook cho sự kiện này trong file settings (macOS/Linux):
{ "hooks": { "MessageDisplay": [ { "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh", "args": [] } ] } ] }}Lưu script này vào .claude/hooks/plain-display.sh trong project và cấp quyền thực thi bằng chmod +x:
#!/bin/bashjq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'Script cần jq trong PATH.
Trên Windows (PowerShell), đăng ký một command hook chạy script qua PowerShell:
{ "hooks": { "MessageDisplay": [ { "hooks": [ { "type": "command", "command": "powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1" ] } ] } ] }}-NoProfile bỏ qua việc nạp PowerShell profile để hook khởi động nhanh, và -ExecutionPolicy Bypass cho phép PowerShell chạy file script local. Lưu script vào .claude/hooks/plain-display.ps1:
$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json$text = $batch.delta -replace '\*\*', '' -replace '`', ''@{ hookSpecificOutput = @{ hookEventName = "MessageDisplay" displayContent = $text }} | ConvertTo-JsonBatch không có markdown đi qua không đổi. Nếu script thất bại, ví dụ vì thiếu jq, Claude Code hiện text gốc và chỉ ghi lỗi vào debug output, không hiện trong phiên.
lượt xem