Mình để Claude Code chạy lệnh mà không hỏi từng lần. Nghe liều, nhưng có một lớp chặn nằm ngoài model: một hook bash. Hôm nay nó chặn đúng lệnh rm -rf mà Claude định chạy, và đó chính là việc nó được viết ra để làm.
Cách hoạt động#
Hook PreToolUse nhận JSON của lệnh sắp chạy qua stdin. Script đọc lệnh, thấy mẫu nguy hiểm thì in lý do ra stderr và thoát với mã 2. Claude nhận lý do đó như một phản hồi rồi tự điều chỉnh.
cmd="$(jq -r '.tool_input.command // empty')"
case "$cmd" in *"# <dấu-xác-nhận>"*) exit 0;; esac
if printf '%s' "$cmd" | grep -Eiq 'rm +-[a-z]*r|docker +compose +down|git +push.*--force'; then
echo "BLOCKED: kiểm tra và backup target trước, rồi chạy lại kèm '# <dấu-xác-nhận>'" >&2
exit 2
fiThủ thuật nằm ở dấu xác nhận#
Mình không cấm hẳn. Lệnh phá huỷ vẫn chạy được, nhưng phải mang theo dấu # <dấu-xác-nhận>. Dấu đó là lời cam kết: đã xem target, đã sao lưu. Claude không thể gõ nó mà không đọc thông báo chặn, nên nó phải dừng lại, ls thư mục, rồi mới chạy tiếp.
Hôm nay chuyện gì xảy ra#
Claude vừa tạo một thư mục clone hỏng và muốn dọn bằng rm -rf. Hook chặn. Nó chạy ls thấy thư mục chỉ có một .git rỗng, báo lại cho mình, rồi chạy lại với # <dấu-xác-nhận>. Chậm thêm một nhịp, và lần này lệnh xóa có lý do được viết ra.
Khuyết điểm: script bắt theo mẫu chữ nên thua lệnh viết vòng vo. Mình coi nó là dây an toàn chứ không phải tường thành.