Hook của Claude Code không chạy trên Windows: nguyên nhân và cách khắc phục
Cùng một hook chạy ổn trên Mac lại không làm gì trên Windows, hoặc tệ hơn, chặn mọi lệnh. Hầu như lúc nào nguyên nhân cũng là shell. Dưới đây là cách Claude Code chạy hook trên Windows và cách làm cho hook của bạn chạy ổn định.
TRẢ LỜI NGẮN
Trên Windows, Claude Code chạy command của hook bằng Git Bash, hoặc bằng PowerShell khi Git Bash chưa được cài. Hãy chỉ rõ bạn muốn dùng cái nào bằng "shell": "powershell" hoặc "shell": "bash", giữ stdout chỉ gồm một đối tượng JSON (không có echo từ profile), dùng mã thoát 2 để chặn, và cập nhật Claude Code (nhiều lỗi hook trên Windows đã được sửa trong các phiên bản đến 2.1.310). Gỡ lỗi bằng /hooks và claude --debug.
Shell nào chạy hook của bạn
Claude Code truyền chuỗi command cho một shell: sh -c trên macOS và Linux, Git Bash trên Windows, hoặc PowerShell khi Git Bash chưa được cài. Chỉ một câu đó trong tài liệu tham khảo hook đã giải thích phần lớn các báo cáo kiểu “trên Mac của tôi vẫn chạy”. Lệnh viết cho PowerShell sẽ lỗi trong Git Bash, script bash sẽ lỗi trong PowerShell, và bạn nhận được shell nào tùy vào việc máy có tình cờ cài Git for Windows hay không.
Loại bỏ việc phỏng đoán bằng trường shell. Trường này nhận "bash" hoặc "powershell":
Nếu bạn muốn dùng Git Bash mà Claude Code không tìm thấy, hãy cài Git for Windows hoặc đặt CLAUDE_CODE_GIT_BASH_PATH trỏ đến vị trí của nó, rồi mở phiên mới.
Triệu chứng và nguyên nhân
| Bạn thấy gì | Nguyên nhân có thể | Cách khắc phục |
|---|---|---|
| Hook có vẻ không bao giờ chạy | Phiên bản Claude Code cũ, sai sự kiện hoặc matcher, JSON không hợp lệ trong cài đặt | Cập nhật; kiểm tra /hooks; xóa chú thích và dấu phẩy thừa ở cuối |
ParserError hoặc “unexpected token” | Cú pháp PowerShell bị chạy trong Git Bash, hoặc ngược lại | Đặt shell một cách tường minh |
| Output của hook “does not start with {” | Có thứ gì đó được in ra trước JSON, thường là từ profile của shell | Tắt output của profile; gửi log ra stderr |
Không tìm thấy đường dẫn trộn lẫn \ và / | Biến đường dẫn Windows bị nối với phần đuôi kiểu Unix | Dùng dạng exec: chương trình trong command, mỗi đối số trong args |
powershell: Permission denied từ Git Bash | Được báo cáo xảy ra không thường xuyên ở 2.1.283 và 2.1.304–2.1.305 (#103910, vẫn đang mở) | Dùng "shell": "powershell" để không phải đi qua Git Bash |
| Hook bảo vệ cho mọi thứ đi qua | Mã thoát 1 thay vì 2, hoặc lỗi fail-open ở các phiên bản cũ | Thoát với mã 2 để chặn; cập nhật lên 2.1.310 trở lên |
| Hook kết thúc ngay lập tức mà không có output | Một chương trình thuộc subsystem GUI mà shell không chờ | Dùng chương trình console, hoặc chờ tiến trình kết thúc |
Chỉ JSON trên stdout
Claude Code đọc output chuẩn của hook làm câu trả lời. Quy tắc trong tài liệu rất ngắn: không in gì ra stdout ngoài đối tượng JSON. Trên Windows, quy tắc này bị phá vỡ một cách khó nhận ra: Git Bash và một số thiết lập vẫn nạp profile của shell, và một lệnh echo vô điều kiện trong profile đó sẽ bị chèn vào trước JSON của bạn. Khi đó hook “chạy được” trong terminal nhưng lại lỗi bên trong Claude Code.
Thay vào đó, hãy ghi log ra tệp hoặc ra stderr, và chỉ cho profile in output khi shell ở chế độ tương tác.
Mã thoát: 2 thì chặn, 1 thì không
Thoát với 0 là thành công. Thoát với 2 là lỗi chặn: với các sự kiện có thể chặn, Claude Code dừng hành động và hiển thị stderr của bạn. Thoát với 1 mà không có JSON hợp lệ là lỗi không chặn, và hành động vẫn tiếp tục. Nếu hook của bạn thực thi một chính sách, hãy dùng exit 2.
Không phải sự kiện nào cũng chặn được theo cách này. Mã thoát của hook Notification bị bỏ qua. Hook Stop thoát với mã 2 sẽ khiến Claude tiếp tục làm việc. Hook PermissionRequest bỏ qua hoàn toàn exit 2. Nó quyết định thông qua một đối tượng decision trong JSON.
Claude Code 2.1.309 đã sửa lỗi hook PowerShell không chặn khi chương trình của hook thoát với mã 2. Nếu hook bảo vệ PowerShell của bạn có vẻ bị bỏ qua, hãy kiểm tra phiên bản trước tiên.
Các bản sửa lỗi Windows đáng để cập nhật
Changelog của Anthropic có một chuỗi dài các bản sửa lỗi hook trên Windows. Những bản giải thích được các chủ đề cũ trên diễn đàn:
| Phiên bản | Bản sửa |
|---|---|
| 2.1.47 | Hook PreToolUse và PostToolUse thất bại âm thầm trên Windows; giờ chúng chạy bằng Git Bash thay vì cmd.exe |
| 2.1.161 | Hook gọi bash một cách tường minh (/usr/bin/bash script.sh) thất bại với “command not found” |
| 2.1.299 | Hook PreToolUse và PermissionRequest không đặt shell bị fail-open khi không có Git for Windows; giờ hook bảo vệ bị lỗi sẽ chặn |
| 2.1.309 | Hook PowerShell không chặn khi gặp mã thoát 2; hook bảo vệ dùng "shell": "powershell" bị fail-open khi không có Git for Windows |
| 2.1.310 | Hook bảo vệ có lệnh chỉ gồm các chuỗi trong dấu ngoặc kép (chẳng hạn một đường dẫn có ngoặc kép) bị fail-open khi không có Git Bash |
Issue cũ có tiêu đề “No hook is working on Windows” (#10450) được báo cáo ở 2.0.27; một người bình luận đã truy ra nguyên nhân là stdin không đến được lệnh của hook. Nếu bạn đang dùng phiên bản cũ đến vậy, hãy cập nhật trước khi gỡ lỗi bất cứ thứ gì khác.
Gọi chương trình trực tiếp
Nếu hook của bạn là một chương trình đã biên dịch, bạn có thể bỏ qua shell bằng dạng exec (trường args; shell bị bỏ qua khi đã đặt args). Trên Windows, dạng exec yêu cầu command phải trỏ tới một tệp thực thi thật như .exe; các shim .cmd và .bat (bao gồm phần lớn những gì npm cài đặt) không thể khởi chạy nếu không có shell. Hãy trỏ tới .exe, hoặc dùng dạng shell.
Một bài học từ việc tự xây dựng công cụ hỗ trợ hook của chúng tôi: trên Windows, một chương trình được build cho subsystem GUI sẽ không được chờ khi shell khởi chạy nó. Shell trả về ngay, agent đọc được output rỗng, và hook trông như không hoạt động. Chương trình hook phải là chương trình console.
Hook của plugin và đường dẫn
Hook của plugin tạo đường dẫn kiểu ${CLAUDE_PLUGIN_ROOT}/hooks/run.sh bị hỏng trên Windows từ v2.1.2 đến v2.1.12 (#18610): biến này được mở rộng thành đường dẫn dùng dấu gạch chéo ngược, và việc thêm phần đuôi dùng dấu gạch chéo xuôi tạo ra một đường dẫn hỗn hợp mà bash không phân giải được. Tài liệu hiện tại đưa ra cách sửa gọn gàng: dùng dạng exec mỗi khi hook tham chiếu một placeholder đường dẫn. Đặt chương trình trong command và mỗi đối số trong args, ví dụ "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js"]. Mỗi phần tử được truyền thành một đối số, không có shell nào làm sai lệch nó.
Danh sách kiểm tra khi gỡ lỗi
/hooksmở danh sách chỉ đọc gồm mọi hook đã cấu hình và nguồn gốc của chúng.claude --debug(hoặc/debuggiữa phiên) hiển thị từng lần chạy hook, output và mã thoát của nó.claude --debug-file <path>ghi thông tin đó ra tệp.- Kiểm tra tính hợp lệ của JSON. Tệp cài đặt là JSON nghiêm ngặt; chỉ một dấu phẩy thừa ở cuối cũng vô hiệu hóa toàn bộ tệp.
- Kiểm tra matcher. Matcher phân biệt chữ hoa chữ thường;
"*",""hoặc không có matcher sẽ khớp mọi thứ. - Tự chạy lệnh đó trong cùng shell mà Claude Code sẽ dùng, với JSON mẫu được pipe vào.
- Xem lại timeout. Hook dạng lệnh mặc định có timeout 600 giây; hook chờ một người phản hồi cần khoảng thời gian dư đó.
Để tắt mọi hook trong lúc điều tra, hãy đặt "disableAllHooks": true.
Nếu bạn chỉ cần thông báo và phê duyệt
Nhiều thiết lập hook trên Windows tồn tại chỉ vì một lý do: biết khi nào Claude Code cần bạn và trả lời nó. Pet Island làm việc đó mà không cần viết script. Nó kết nối với Claude Code chỉ bằng một cú nhấp, tự xử lý các chi tiết của Windows giúp bạn, và đưa mọi yêu cầu cấp quyền, câu hỏi và kế hoạch lên một island nhỏ ở phía trên màn hình, kèm lệnh hoặc diff, nơi bạn cho phép hoặc từ chối. Nó cũng hoạt động khi Claude Code chạy bên trong WSL.
Câu hỏi thường gặp
Shell nào chạy hook của Claude Code trên Windows?
Git Bash, hoặc PowerShell khi Git Bash chưa được cài. Đặt "shell": "powershell" hoặc "shell": "bash" trong hook để chọn một cách tường minh.
Vì sao hook Claude Code của tôi chạy được trong terminal nhưng không chạy trong Claude Code?
Thường là do output thừa: một profile shell in ra văn bản sẽ bị chèn vào trước JSON của hook. Chỉ in đối tượng JSON ra stdout.
Làm sao để hook chặn một lệnh gọi công cụ?
Thoát với mã 2 (hoặc trả về quyết định deny trong JSON). Mã thoát 1 là lỗi không chặn và hành động vẫn tiếp tục. Hook PermissionRequest bỏ qua exit 2 và dùng đối tượng decision thay thế.
Lệnh của hook có thể là tệp .cmd hoặc .bat không?
Không được ở dạng exec. Trên Windows, lệnh phải trỏ tới một tệp thực thi thật như .exe. Dùng dạng shell để chạy tệp .cmd hoặc .bat.
Làm sao để biết vì sao hook thất bại?
Chạy claude --debug hoặc gõ /debug, và dùng /hooks để xem những hook nào đã được nạp.
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.
- Tài liệu Claude Code: Hooks reference
- Tài liệu Claude Code: Hooks guide
- Claude Code: CHANGELOG
- Issue #10450: No hook is working on Windows
- Issue #18610: Plugin hooks cannot execute scripts on Windows
- Issue #103910: Hooks intermittently fail to spawn powershell (Git Bash)