Hook đã được cấu hình, lệnh /hooks hiển thị nó, nhưng sau đó bắt đầu có gì đó kỳ lạ. Bạn gửi một yêu cầu, nhưng dòng mới không xuất hiện trong requests.log. Hoặc xuất hiện hai lần. Đôi khi tệ hơn: hành động tự động chạy sau mọi lần sửa và bắt đầu gây cản trở cho công việc thông thường.
Trong tình huống này không cần dựng lại toàn bộ cấu hình từ đầu. Sẽ hữu ích hơn nếu biến quan sát thành một nhiệm vụ nhỏ, có thể kiểm chứng: cái gì phải kích hoạt hook, dấu vết nào phải xuất hiện, và trường hợp nào nó bắt buộc phải bỏ qua. Sau đó Claude Code có thể kiểm tra các tệp cụ thể và đề xuất một chỉnh sửa tối thiểu.

Khi nào hook phải kích hoạt
Trong bài này chúng ta phân tích các command hook từ tệp cấu hình. Một hook như vậy có ba phần quan trọng: sự kiện, điều kiện kích hoạt và bộ xử lý. Sự kiện trả lời câu hỏi "khi nào kiểm tra", điều kiện thu hẹp các trường hợp phù hợp, bộ xử lý thực hiện hành động.
Ví dụ, nhật ký yêu cầu đăng ký theo dõi UserPromptSubmit. Sự kiện này xảy ra khi phiên nhận được một yêu cầu, trước cả khi mô hình xử lý nó. Thường đó là yêu cầu của bạn, nhưng Claude Code cũng kích hoạt sự kiện này ở những lượt nó tự bắt đầu: vòng mới của /loop, báo cáo của subagent chạy nền. Vì vậy dòng mới trong nhật ký xác nhận rất ít - hook đã nhận yêu cầu và ghi lại, không hơn. Agent có thực hiện nhiệm vụ đúng hay không thì dòng này không cho biết.
UserPromptSubmit không có matcher. Nếu nhật ký chỉ cần một số yêu cầu nhất định, chính bộ xử lý sẽ lọc chúng. Với các sự kiện liên quan đến công cụ thì khác. Ví dụ, matcher Edit|Write chọn công cụ, còn đường dẫn tệp được kiểm tra riêng theo dữ liệu của lệnh gọi. Matcher với tên công cụ không có nghĩa là "chỉ các tệp trong src".
Sự kiện Stop có nghĩa là Claude đã trả lời xong. Đây là thời điểm thuận tiện để thông báo, nhưng không phải bằng chứng cho thấy mã đúng hay nhiệm vụ đã hoàn thành thành công.
Trước khi sửa lỗi, hãy ghi lại hai kịch bản ngắn:
- cần thiết: gửi một yêu cầu thử nghiệm và thấy một dòng mới trong
requests.log; - thừa: mở tệp trong trình soạn thảo thông thường, không gửi yêu cầu mới tới Claude Code, và xác nhận nhật ký không thay đổi. Với nhật ký ghi mọi yêu cầu, prompt thứ hai được gửi cũng phải tạo ra một bản ghi; nó không dùng được làm đối chứng âm tính.
Đây là các tiêu chí kiểm tra. Không cần nghĩ trước câu trả lời cụ thể của Claude Code.
Bản ghi không xuất hiện
Bắt đầu từ chỗ quan sát khác với kỳ vọng. Với nhật ký, đó là việc thiếu dòng mới sau yêu cầu thử nghiệm. Sau đó đi từ cấu hình tới hành động:
- Nhập
/hookstrong phiên tương tác của Claude Code và kiểm tra xem bản ghi cần thiết đã được nạp chưa. Menu này do chính bạn mở; hãy chuyển những gì bạn thấy ở đó cho agent. - Đối chiếu sự kiện. Nhật ký yêu cầu phải phản ứng với
UserPromptSubmit, còn hành động sau khi sửa tệp thường gắn với sự kiện liên quan đến công cụ. - Tìm tệp cấu hình gốc. Đó có thể là cấu hình cấp người dùng, cấp dự án hoặc cấp cục bộ; hãy so sánh không chỉ tên mà cả lệnh đầy đủ. Việc cùng một bộ xử lý xuất hiện trong nhiều tệp cấu hình cùng lúc tự nó chưa có nghĩa là sẽ chạy hai lần.
- Kiểm tra lệnh của bộ xử lý: tệp có tồn tại không, lệnh có phù hợp với hệ điều hành của bạn không, đường dẫn có khoảng trắng có bị hỏng không.
- Kiểm tra nơi lưu kết quả. Đường dẫn tương đối phụ thuộc vào thư mục làm việc của tiến trình, vì vậy bộ xử lý có thể đã ghi tệp không đúng nơi bạn đang tìm.
Cách làm chính ở đây là thông qua agent. Hãy chuyển cho nó quan sát của bạn, kết quả kiểm tra /hooks và giới hạn của chỉnh sửa:
Hook UserPromptSubmit hiển thị trong /hooks, nhưng sau yêu cầu thử nghiệm
requests.log không có dòng mới.
Hãy tìm cấu hình gốc và bộ xử lý. Kiểm tra sự kiện, đường dẫn tới lệnh,
thư mục làm việc và việc tạo thư mục cho nhật ký. Chỉ ra nguyên nhân
và thực hiện chỉnh sửa tối thiểu. Giữ nguyên các hook và cấu hình còn lại.
Sau khi sửa, mô tả kịch bản kiểm tra cần thiết và kịch bản đối chứng âm tính.Hãy yêu cầu agent nêu tên tệp và các dòng chính xác mà nó thay đổi. Nhờ vậy bạn sẽ thấy nó sửa cấu hình gốc hay một bản sao thừa nào đó hoàn toàn không được nạp.
Ví dụ, agent phát hiện: bộ xử lý ghi vào logs/requests.log, nhưng thư mục logs/ không tồn tại và script không tạo ra nó. Chỉnh sửa tối thiểu là tạo thư mục này trước khi ghi. Sau đó hãy kiểm tra đúng logs/requests.log: một yêu cầu thử nghiệm phải thêm đúng một bản ghi. Đây là ví dụ về một nguyên nhân khả dĩ, không phải chẩn đoán cho mọi nhật ký trống.
Nếu cấu hình có trong tệp nhưng không xuất hiện trong /hooks, nguyên nhân có thể nằm ở cú pháp JSON, phạm vi cấu hình hoặc ở chỗ hiện tại hook hoàn toàn không chạy: thư mục dự án chưa được đánh dấu là tin cậy, disableAllHooks đang bật, hoặc quản trị viên chỉ cho phép hook từ chính sách được quản lý. Hook từ các tệp cấu hình khác nhau được cộng dồn, không thay thế lẫn nhau. Nếu bản ghi đã được nạp nhưng không có dấu vết, sự chú ý chuyển sang lệnh, dữ liệu đầu vào và đường dẫn đầu ra.
Ý nghĩa của nhánh rẽ này đơn giản: chừng nào Claude Code chưa nạp được bản ghi, còn quá sớm để phân tích hoạt động của script. Khi bản ghi đã hiển thị, cấu hình đã đến được ứng dụng, và lúc đó hữu ích hơn là kiểm tra việc chạy bộ xử lý, JSON đầu vào và nơi kết quả phải xuất hiện.
Bộ xử lý kết thúc bằng lỗi
Command hook nhận JSON qua đầu vào tiêu chuẩn (stdin). Trong đó có các trường chung của sự kiện và dữ liệu riêng của từng sự kiện cụ thể. Với UserPromptSubmit, nội dung yêu cầu đến trong trường prompt. Văn bản này cần được đọc như dữ liệu. Không được nối nó với một shell command rồi thực thi: yêu cầu thử nghiệm có thể chứa dấu ngoặc kép, ký tự xuống dòng và bất kỳ văn bản thông thường nào.
Hãy nhờ Claude Code tái hiện việc chạy bộ xử lý trên một đầu vào giả định. Trong yêu cầu, hãy nêu ngay các trường hợp quan trọng:
Kiểm tra bộ xử lý hook tách riêng khỏi phiên làm việc, trên JSON giả định.
Cần các trường hợp: prompt thông thường và nhiều dòng, đường dẫn dự án có khoảng trắng,
thư mục nhật ký không tồn tại và đầu vào không hợp lệ.
Không thực thi văn bản prompt như mã. Hãy cho xem lệnh chạy, mã thoát,
stderr và thay đổi thực tế của tệp thử nghiệm. Không thay đổi nhật ký đang dùng thật.Mã thoát giúp hiểu chuyện gì đã xảy ra với chính bộ xử lý, nhưng ý nghĩa của nó phụ thuộc vào sự kiện. exit 0 cho biết lệnh của hook kết thúc không lỗi. Điều này không xác nhận kết quả của toàn bộ nhiệm vụ. exit 2 có thể chặn hành động ở các sự kiện "trước khi", còn sau khi công cụ đã thực thi thì nó không còn có thể hủy công việc đó. Vì vậy không thể áp dụng mù quáng cùng một công thức với exit 2 cho mọi sự kiện.
Nếu lần chạy riêng lẻ thành công, mà trong phiên làm việc hook vẫn im lặng, hãy chạy Claude Code từ thư mục dự án bằng lệnh claude --debug-file hook-debug.log và lặp lại một yêu cầu thử nghiệm. Mở tệp hook-debug.log vừa xuất hiện và tìm sự kiện, các hook khớp và mã thoát. Đây là nhật ký của chính Claude Code, tách biệt với requests.log của bạn. Sau khi kiểm tra, hãy kết thúc phiên chẩn đoán này; cho công việc thông thường, hãy chạy Claude Code không có cờ này. stdout thông thường của phần lớn sự kiện đi vào debug log và không nhất thiết xuất hiện trong transcript. Các ngoại lệ phụ thuộc vào sự kiện, vì vậy các dòng chẩn đoán tùy ý trong stdout có thể ảnh hưởng đến ngữ cảnh thay vì trở thành một nhật ký tiện lợi.
Cho lần phân tích đầu tiên, an toàn hơn là ghi dấu chẩn đoán vào một tệp cục bộ riêng, rồi xóa nó sau khi kiểm tra xong. Tài liệu chính thức không có cờ dry-run chung cho hook; chế độ thử nghiệm được làm thành một phần của một bộ xử lý học tập cụ thể.
Hook kích hoạt thừa
Hai dòng giống nhau sau một yêu cầu có thể cho thấy việc đăng ký trùng lặp: các hook tương tự nhưng không hoàn toàn giống nhau nằm, chẳng hạn, trong cấu hình cấp người dùng và cấp dự án. Nếu đó đúng là cùng một lệnh, chỉ đơn giản bị lặp lại trong các tệp khác nhau, nó vẫn chỉ chạy một lần. Các bản sao đến từ plugin hoặc skill được tính riêng, vì vậy nguồn gốc trong menu cũng quan trọng. Trước tiên hãy chuyển cho agent kết quả /hooks và yêu cầu liệt kê các bản ghi của sự kiện này trong tất cả tệp cấu hình có sẵn. Nếu cấu hình ổn, hãy kiểm tra xem chính bộ xử lý có ghi hai lần trong một lần chạy hay không. Đừng vội xóa dòng đầu tiên tìm thấy.
Một nguyên nhân phổ biến khác là điều kiện quá rộng. Giả sử hook phải để lại dấu chẩn đoán sau khi sửa các tệp trong src/, nhưng lại phản ứng với mọi lần dùng Edit và Write. Ở đây cần hai lớp lọc:
- matcher chọn công cụ
EditvàWrite; - một điều kiện riêng kiểm tra đường dẫn từ đối số của công cụ và bỏ qua mọi thứ nằm ngoài
src/.
Một phép kiểm tra không thay thế cho phép kia. Matcher loại bỏ các công cụ không phù hợp, sau đó điều kiện theo đối số chỉ giữ lại các đường dẫn trong src/. Dấu chẩn đoán chỉ xuất hiện khi cả hai phép kiểm tra đều cho kết quả "có".
Một đoạn cấu hình có thể trông như thế này:
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "python .claude/hooks/mark-source-edit.py" }
]
}
]Đây là một phần tử trong đối tượng hooks nằm trong settings.json đã có sẵn, không phải toàn bộ tài liệu JSON. Việc kiểm tra đường dẫn phải nằm trong bộ xử lý; script mark-source-edit.py ở đây chỉ là giả định, nội dung của nó không có trong bài viết. Lệnh này giả định python có sẵn và chạy từ thư mục gốc của dự án. Agent phải đối chiếu trình thông dịch, shell và đường dẫn trong cấu hình của bạn; không nên sao chép ví dụ này như một kết nối sẵn dùng.
Đoạn cấu hình này dễ đọc nếu coi như một câu:
- Sau khi sửa tệp thành công
- Nếu công cụ là Edit hoặc Write
- Chạy mark-source-edit.py
Chuỗi này chỉ cho thấy những gì được khai báo trong cấu hình. Quyết định "để lại dấu hay bỏ qua tệp" là do chính bộ xử lý đưa ra sau khi kiểm tra đường dẫn. Vì vậy sự có mặt của matcher xác nhận việc chọn công cụ, nhưng chưa chứng minh việc lọc src/ là đúng.
| Hành động | Dấu vết kỳ vọng | Điều gì được kiểm tra |
|---|---|---|
Sửa src/card.js qua Edit | Một dấu chẩn đoán mới | Đúng công cụ và đúng đường dẫn thỏa điều kiện |
Tạo src/theme.css qua Write | Một dấu chẩn đoán mới | Thao tác được phép thứ hai cũng được tính |
Sửa README.md qua Edit | Không có dấu mới | Tệp ngoài src/ bị loại |
Trong bảng này, Edit và Write nghĩa là các công cụ của Claude Code. Sửa tệp bằng tay trong trình soạn thảo không kích hoạt chúng, và vì vậy không kiểm tra được matcher này. Trước mỗi hành động, hãy đánh dấu điểm cuối của tệp chẩn đoán, sau đó chỉ so sánh các bản ghi mới.
Chừng nào ranh giới chưa được kiểm chứng, hãy giữ hành động ở mức vô hại: một dấu ghi nhận việc kích hoạt, không định dạng, không chặn hay sửa hàng loạt tệp. Trước tiên hãy đảm bảo trường hợp cần thiết chạy đúng một lần, còn trường hợp thừa thì không chạy.
Việc kích hoạt thừa luôn tạo thêm công việc. Trong khi đó, một lệnh cục bộ chỉ đơn giản ghi tệp thì bản thân nó không gọi đến mô hình. Hook kiểu prompt hoặc agent hoạt động khác và có dùng mô hình. Không cần phân tích chi tiết chi phí ở đây, chỉ cần không nhầm lẫn hai loại bộ xử lý này với nhau.
Cách tắt phản ứng
Nếu hook đang gây cản trở ngay lúc này, hãy tìm bản ghi gốc của nó và chỉ xóa đúng bản ghi đó khỏi mảng của sự kiện tương ứng. Giữ nguyên các hook còn lại. Đây là việc tắt các lần chạy trong tương lai: nó không hủy lệnh đã thực thi, không khôi phục tệp đã bị thay đổi và không xóa những dòng đã kịp lọt vào nhật ký.
Trước khi xóa, hãy lưu lại một bản sao của bản ghi đã chọn nếu bạn định khôi phục nó sau này. Sau khi thay đổi, mở lại /hooks và xác nhận bản ghi đó không còn nữa. Nếu menu vẫn hiển thị cấu hình cũ, hãy kết thúc phiên làm việc và mở phiên mới từ cùng dự án. Sau đó thực hiện một hành động kiểm chứng và so sánh nhật ký trước và sau. Không được có dòng mới nào từ hook đã tắt.
Để tạm thời tắt tất cả hook, có một cấu hình riêng là "disableAllHooks": true. Nhưng đây là công tắc chung, không phải cách tắt riêng một bản ghi. Với chính cấu hình này, thứ tự ưu tiên của các tệp vẫn có tác dụng: "disableAllHooks": false trong tệp của dự án sẽ ghi đè true trong cấu hình người dùng của bạn. Ngoài ra, các hook do chính sách quản lý của tổ chức thiết lập không thể tắt bằng cấu hình cấp người dùng hay cấp dự án thông thường.
Hook đã tắt sẽ ngừng chạy trong tương lai. Nếu bộ xử lý đã thay đổi một tệp, việc khôi phục tệp đó được thực hiện riêng và chỉ sau khi xem diff hoặc phiên bản đã lưu.
Cách giao việc sửa lỗi cho Claude Code
Một yêu cầu tốt bao gồm quan sát, sự kiện kỳ vọng, các tệp cụ thể và giới hạn của thay đổi. Chừng đó là đủ để agent nghiên cứu cấu hình mà không viết lại toàn bộ hệ thống tự động hóa:
Hãy xử lý hook trong dự án này.
Quan sát: sau một yêu cầu thử nghiệm, requests.log nhận được hai dòng.
/loop và subagent chạy nền không được khởi chạy trong phiên này.
Kỳ vọng: UserPromptSubmit phải thêm đúng một dòng.
Tôi sẽ đính kèm kết quả /hooks. Hãy kiểm tra tất cả nguồn cấu hình có sẵn
và lệnh của bộ xử lý.
Trước tiên hãy nêu nguyên nhân và các tệp cần thay đổi. Sau đó thực hiện
chỉnh sửa tối thiểu, giữ nguyên các hook còn lại và cho xem diff.
Kiểm tra trường hợp cần thiết và trường hợp đối chứng âm tính. Sau đó cho xem
cách chỉ tắt đúng phản ứng này.Cuối cùng, bạn cần có được bốn câu trả lời rõ ràng:
- sự kiện nào kích hoạt hook;
- điều kiện nào bỏ qua các trường hợp thừa;
- cấu hình gốc và bộ xử lý nằm ở đâu;
- làm sao để tắt đúng phản ứng này mà không ảnh hưởng đến các phản ứng khác.
Và toàn bộ tuyến kiểm chứng: hành động cần thiết để lại đúng một dấu vết kỳ vọng, hành động thừa không để lại dấu vết nào, lỗi của bộ xử lý được thấy rõ trong chẩn đoán có mục tiêu, sau khi tắt thì không còn dấu vết mới xuất hiện. Tất cả khá đời thường. Chính vì vậy một hook như thế dễ duy trì hơn nhiều so với một hệ thống tự động hóa chỉ "thỉnh thoảng làm gì đó".
Để đối chiếu hành vi, hãy dùng tài liệu tham khảo về hooks và hướng dẫn thực hành chính thức: giao diện thay đổi cùng với Claude Code.
Hành vi đã được đối chiếu với tài liệu chính thức vào ngày 18 tháng 9 năm 2026. Các ví dụ chỉ minh họa thứ tự kiểm tra; không có bộ xử lý hoàn chỉnh nào ở đây. Hãy kiểm tra lệnh và đường dẫn trên hệ điều hành và shell của bạn.
GO TO FULL VERSION