Hook trong Cursor: hướng dẫn đầy đủ, và vì sao đôi khi chúng không kích hoạt

Hook trong Cursor cho phép một script theo dõi hoặc chặn những gì agent làm: mọi lệnh shell, lệnh gọi MCP, thao tác đọc và chỉnh sửa tệp. Chúng rất mạnh, nhưng lại thất bại một cách âm thầm. Đây là tất cả những gì chúng tôi học được khi triển khai chúng trong thực tế.

Tác giả: Aslan, nhóm Pet Island · Cập nhật 26/10/2026 · 7 phút đọc

TRẢ LỜI NGẮN

Đặt một tệp hooks.json với "version": 1 trong ~/.cursor/ (mọi dự án) hoặc .cursor/ trong một repo, trỏ mỗi sự kiện đến một lệnh, và in JSON ra stdout. Dùng beforeShellExecution và beforeMCPExecution để allow, deny hoặc ask, và đặt failClosed: true nếu một hook bị hỏng phải chặn thay vì để lệnh đi qua. Khi một hook có vẻ bị bỏ qua, hãy mở output channel Hooks trước tiên: phần lớn sự cố là do đường dẫn, workspace chưa được tin cậy, JSON không hợp lệ, hoặc một sự kiện mà Cursor CLI chưa gửi.

Hook trong Cursor là gì

Hook là một lệnh mà Cursor chạy tại một điểm cố định trong vòng lặp của agent. Cursor gửi một đối tượng JSON mô tả việc sắp diễn ra vào đầu vào chuẩn của lệnh, chờ, rồi đọc câu trả lời JSON từ đầu ra chuẩn của nó. Một số hook chỉ quan sát (ghi log thao tác chỉnh sửa, định dạng tệp). Số khác thì ra quyết định: một hook beforeShellExecution có thể cho lệnh chạy, chặn nó, hoặc hỏi bạn trước.

Điều đó khiến hook trở thành công cụ phù hợp cho ba việc: rào chắn an toàn (không bao giờ chạy rm -rf hay git push --force khi không có người duyệt), tự động hóa (định dạng hoặc lint sau mỗi lần chỉnh sửa), và khả năng quan sát (ghi log hoặc chuyển tiếp những gì agent làm để bạn biết khi nào nó cần bạn).

hooks.json nằm ở đâu

Cursor đọc hook từ bốn nơi và chạy tất cả. Mọi hook khớp từ mọi nguồn đều chạy, và Cursor gộp các câu trả lời của chúng lại.

CấpTệpGhi chú
Người dùng~/.cursor/hooks.jsonÁp dụng cho mọi dự án. Lệnh tương đối được phân giải từ ~/.cursor/.
Dự án<project>/.cursor/hooks.jsonĐược commit vào repo. Chỉ chạy trong workspace tin cậy. Lệnh tương đối được phân giải từ thư mục gốc của dự án.
NhómBảng điều khiển web của CursorChỉ dành cho Enterprise, được đồng bộ đến mọi thành viên.
EnterpriseC:\ProgramData\Cursor\hooks.json (Windows), /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL)Do bộ phận IT quản lý.

Khi các câu trả lời mâu thuẫn, câu trả lời nghiêm ngặt nhất sẽ thắng: một deny từ bất kỳ nguồn nào thắng ask, và ask thắng allow. Tệp nhỏ nhất chạy được trông như sau (đây là ví dụ trong tài liệu của Cursor):

~/.cursor/hooks.jsonSao chép
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": "./hooks/format.sh" }]
}
}

version phải là 1. Cursor theo dõi tệp này và nạp lại khi bạn lưu. Nếu một thay đổi không có hiệu lực, hãy khởi động lại Cursor. Chỉ riêng việc đó đã giải quyết được một số lượng đáng ngạc nhiên các báo cáo trên diễn đàn.

Các sự kiện bạn có thể gắn hook

Danh sách hiện tại từ tài liệu của Cursor, được nhóm theo mục đích:

NhómSự kiệnCó chặn được không?
Shell và MCPbeforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecutionHai hook before…: allow, deny hoặc ask
Mọi công cụpreToolUse, postToolUse, postToolUseFailurepreToolUse: allow hoặc deny (hiện chưa thực thi ask)
TệpbeforeReadFile, afterFileEditbeforeReadFile: allow hoặc deny
PromptbeforeSubmitPromptCó, với continue: false
Vòng lặp agentstop, afterAgentResponse, afterAgentThought, preCompactKhông (stop có thể gửi một tin nhắn tiếp theo)
SubagentsubagentStart, subagentStopsubagentStart: allow hoặc deny (ask được tính là deny)
Phiên và ứng dụngsessionStart, sessionEnd, workspaceOpenKhông
Gợi ý hoàn thành TabbeforeTabFileRead, afterTabFileEditbeforeTabFileRead: allow hoặc deny

Mọi sự kiện đều nhận một số trường chung (conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots) cùng các trường riêng của nó. beforeShellExecution nhận đầy đủ command và cwd của lệnh; stop nhận status là completed, aborted hoặc error.

Allow, deny và ask

Một hook ra quyết định sẽ in ra một đối tượng JSON. Với hook shell, nó trông như sau:

stdout của một hook beforeShellExecutionSao chép
{
"permission": "ask",
"user_message": "This pushes to main. Run it?",
"agent_message": "The user wants to confirm pushes."
}
  • permission là allow, deny hoặc ask.
  • user_message là nội dung bạn thấy trong Cursor.
  • agent_message là nội dung được báo cho agent, để nó đổi hướng thay vì thử lại cùng một lệnh.

Mã thoát quan trọng không kém JSON. Thoát với 0 nghĩa là “dùng JSON của tôi”. Thoát với 2 chặn hành động, giống như deny. Mọi mã thoát khác đều bị coi là hook thất bại, và hành động vẫn tiếp tục, trừ khi bạn đặt failClosed. Với hook quyền, JSON không hợp lệ hoặc không khớp schema sẽ chặn hành động.

Tài liệu của Cursor ghi rằng ask được hỗ trợ cho beforeShellExecution và beforeMCPExecution. Vào tháng 3 năm 2026, nhân viên Cursor viết trên diễn đàn rằng hook chỉ thực thi deny, còn allow và ask thì “basically ignored” (về cơ bản bị bỏ qua), nhất là khi bật danh sách cho phép lệnh hoặc sandbox. Chúng tôi chưa tìm thấy thông báo sửa lỗi nào. Nếu một rào chắn bắt buộc phải giữ vững, hãy trả về deny thay vì ask, và kiểm tra trên phiên bản của chính bạn.

failClosed và timeout

Mỗi mục hook có thể kèm theo các tùy chọn bên cạnh command:

.cursor/hooks.jsonSao chép
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": "./.cursor/hooks/guard.sh",
"timeout": 30,
"failClosed": true
}
]
}
}
  • failClosed: true biến một lần crash, timeout, mã thoát khác 0 hoặc output rỗng thành lệnh chặn. Mặc định là false: một hook bảo vệ bị hỏng sẽ âm thầm cho mọi thứ đi qua. Với hook an toàn, hầu như lúc nào bạn cũng muốn true.
  • timeout tính bằng giây. Hãy chừa đủ thời gian nếu hook của bạn chờ con người: một người đọc diff cần hơn năm giây.
  • matcher lọc thời điểm hook chạy. Với hook shell, nó được so khớp với toàn bộ chuỗi lệnh; với hook công cụ, nó được so khớp với loại công cụ, chẳng hạn Shell, Read, Write hoặc MCP:<tool_name>.

Có một sự đánh đổi. Vào tháng 7 năm 2026, một người dùng macOS gặp lỗi “MainThreadShellExec not initialized”: shell host của chính Cursor chưa sẵn sàng, và vì hook ở chế độ fail-closed nên shell của agent bị chặn. Nhân viên Cursor cho biết bản sửa đã được phát hành và khuyến nghị giữ failClosed: true. Nếu bạn gặp lỗi đó trên bản build cũ hơn, “Developer: Reload Window” là cách xử lý tạm thời.

Hook trên Windows

Trên Windows, ứng dụng desktop Cursor chạy lệnh hook qua PowerShell và pipe JSON vào. Script PowerShell đọc nó từ $input; các chương trình khác đọc đầu vào chuẩn như bình thường. Có ba điều dễ khiến người dùng mắc lỗi:

  1. Script Bash không chạy được nguyên trạng. Một tệp guard.sh từ đồng nghiệp dùng macOS sẽ lỗi khi chạy dưới PowerShell. Hãy dùng script .ps1, một chương trình đã biên dịch, hoặc gọi trình thông dịch một cách tường minh (python .cursor/hooks/guard.py). Một chủ đề trên diễn đàn từ tháng 1 năm 2026 cho thấy một thiết lập Git Bash thất bại với syntax error near unexpected token '[Convert]::FromBase64String', tức là mã PowerShell bị chạy trong bash.
  2. Execution policy. Nếu PowerShell từ chối chạy script, hãy khởi chạy chúng bằng powershell -NoProfile -ExecutionPolicy Bypass -File …, in JSON ra, và kết thúc bằng exit 0.
  3. Chương trình GUI trả quyền điều khiển quá sớm. Vấn đề này đã tốn của chúng tôi cả một ngày. Nếu hook của bạn là một chương trình Windows được build cho subsystem GUI, PowerShell sẽ khởi chạy nó rồi đi tiếp mà không chờ. Cursor đọc được output rỗng, nên hook trông như không làm gì (hoặc, khi có failClosed, chặn mọi thứ). Hãy build hook dưới dạng chương trình console, hoặc bọc nó lại để PowerShell chờ đến khi nó thoát.

Hook cấp dự án cũng cần workspace tin cậy. Một nhân viên Cursor đã liệt kê những gì cần kiểm tra khi chúng không làm gì: dòng log Hooks “Hook N produced no output”, mục Settings → Hooks hiển thị workspace là tin cậy, và các lỗi như spawn, ENOENT, EPERM hoặc ExecutionPolicy trong console.

Hook trong Cursor CLI

Cursor CLI (cursor-agent) đọc cùng các tệp ~/.cursor/hooks.json và .cursor/hooks.json, nhưng chưa bao giờ gửi mọi sự kiện mà trình soạn thảo gửi. Vào tháng 1 năm 2026, chỉ có hai hook shell kích hoạt. Đến tháng 4, nhân viên Cursor xác nhận beforeShellExecution, afterShellExecution, afterFileEdit, postToolUse, stop và sessionStart hoạt động trong CLI, trong khi afterAgentResponse và afterAgentThought vẫn chưa (được xác nhận lại vào tháng 5). Với bất kỳ sự kiện nào không có trong danh sách đó, hãy kiểm tra trước khi dựa vào nó.

Cloud agent còn nghiêm ngặt hơn: hook cấp người dùng từ ~/.cursor/hooks.json hoàn toàn không chạy ở đó.

Khi hook không kích hoạt: danh sách kiểm tra

  1. Mở output channel Hooks (View → Output → Hooks) hoặc tab Hooks trong Customize. Nó cho biết hook nào đã được nạp, mọi lần chạy và các lỗi.
  2. Kiểm tra chính tệp JSON. Có "version": 1, tên sự kiện viết chính xác (phân biệt chữ hoa chữ thường), JSON hợp lệ.
  3. Kiểm tra đường dẫn. Lệnh tương đối được phân giải từ thư mục gốc của dự án đối với hook dự án, và từ ~/.cursor/ đối với hook người dùng.
  4. Tin cậy workspace. Hook dự án không chạy trong thư mục chưa được tin cậy.
  5. Chạy hook thủ công. Pipe một JSON mẫu vào và xem stdout. Bất cứ thứ gì khác ngoài một đối tượng JSON (một lệnh echo lạc chỗ, một cảnh báo) đều làm hỏng việc phân tích cú pháp.
  6. Khởi động lại Cursor. Lưu tệp sẽ nạp lại nó, nhưng một số báo cáo trên diễn đàn chỉ được giải quyết bằng cách khởi động lại hoàn toàn.
  7. Nắm rõ các hạn chế. ask và allow được báo cáo là bị bỏ qua khi có danh sách cho phép và sandbox; subagentStart/subagentStop được báo cáo là không kích hoạt ở một số phiên bản; CLI bỏ qua một số sự kiện. Các commit từ bảng Source Control tích hợp không bị chặn lại: không có sự kiện hook pre-commit.

Hook, auto-run và Auto-review

Hook tách biệt với các thiết lập phê duyệt riêng của Cursor (Settings → Agents → Approvals & Execution). Auto-run và Auto-review quyết định Cursor có hỏi bạn trong bảng điều khiển của nó hay không; hook của bạn bổ sung quyết định riêng bên trên. Đó là lý do báo cáo “hook của tôi trả về ask, nhưng lệnh vẫn cứ chạy” phổ biến đến vậy: một luồng cấp quyền khác đã cho phép nó rồi.

Thêm một bài học chúng tôi rút ra sau khi đã trả giá: với Auto-review, Cursor có thể hỏi về một số lệnh trong cửa sổ của chính nó. Hook không bao giờ thấy câu hỏi đó. Nó diễn ra bên trong Cursor, không phải trong luồng xử lý hook.

Vai trò của Pet Island

Nếu điều bạn muốn từ hook là “hiện cho tôi mọi lệnh rủi ro và để tôi quyết định mà không cần chuyển cửa sổ”, thì đó chính là những gì Pet Island làm với Cursor. Nó cài hook giúp bạn (beforeShellExecution và beforeMCPExecution, với failClosed được bật để helper đã ngừng hoạt động cũng không bao giờ cho lệnh đi qua) và đưa từng yêu cầu lên một island nhỏ ở phía trên màn hình kèm đầy đủ lệnh. Bạn cho phép hoặc từ chối ngay tại đó; agent của Cursor sẽ chờ.

Lệnh và chỉnh sửa tệp từ trình soạn thảo Cursor xử lý được từ island. Câu hỏi và kế hoạch hiển thị trên island, và bạn trả lời chúng trong Cursor. Trong Cursor CLI, lệnh xử lý được từ island; các sự kiện khác phụ thuộc vào những gì CLI gửi. Gỡ cài đặt chỉ xóa các mục mà Pet Island đã thêm; hook riêng của bạn vẫn được giữ nguyên.

Câu hỏi thường gặp

Hook của Cursor có chạy trên Windows không?

Có. Ứng dụng desktop chạy chúng qua PowerShell và truyền JSON qua đầu vào chuẩn ($input trong script PowerShell). Script Bash viết cho macOS cần một phiên bản PowerShell hoặc Python, và chương trình thuộc subsystem GUI phải được bọc lại để PowerShell chờ chúng.

Vì sao hook của tôi trả về ask nhưng lệnh vẫn chạy?

Vào tháng 3 năm 2026, nhân viên Cursor cho biết hook chỉ thực thi deny một cách đáng tin cậy; allow và ask có thể bị danh sách cho phép hoặc sandbox ghi đè. Hãy dùng deny cho mọi thứ không được phép chạy, và kiểm tra output channel Hooks để xem hook của bạn thực sự trả về gì.

failClosed có tác dụng gì?

Khi failClosed được đặt thành true, một lần crash, timeout, mã thoát khác 0 hoặc output rỗng sẽ chặn hành động. Nếu không có nó, một hook bị hỏng sẽ để hành động tiếp tục.

Tôi xem lỗi của hook ở đâu?

Trong output channel Hooks (View → Output → Hooks) và tab Hooks trong Customize.

Hook có chạy trong Cursor CLI không?

Một phần. CLI đọc cùng các tệp hooks.json. Tính đến tháng 5 năm 2026, nhân viên Cursor đã xác nhận các hook shell, afterFileEdit, postToolUse, stop và sessionStart, nhưng không có afterAgentResponse hay afterAgentThought.

Hook có thể chặn một git commit từ bảng Source Control không?

Không. Cursor không có sự kiện hook pre-commit, và các commit thực hiện qua giao diện Source Control tích hợp không bị chặn lại. Hãy dùng một git pre-commit hook thông thường cho việc đó.

Nguồn

Mọi thiết lập và hành vi trên trang này đã được đối chiếu với các trang sau vào 26 tháng 10 năm 2026. Agent thay đổi rất nhanh; nếu có điều gì không còn khớp, trang chính thức là chuẩn.

ĐỌC TIẾP

Trả lời mọi agent ngay từ phía trên màn hình.

Windows và Mac · Miễn phí 7 ngày, sau đó $8.99 trả một lần · $1 từ mỗi lượt mua dùng để nuôi động vật đường phố

Dùng thử Pet Island miễn phí