AI Automation

Claude Code Hooks: tự động format, chặn lệnh nguy hiểm, ép chạy test — không cần nhắc

Tóm tắt

Hook là lệnh shell (hoặc HTTP, prompt…) chạy tại các sự kiện như PreToolUse, PostToolUse, Stop. Exit code 2 ở PreToolUse chặn tool call và gửi lý do cho Claude; ở Stop thì buộc Claude làm tiếp. Cấu hình trong .claude/settings.json, xem bằng /hooks.

Bạn có thể dặn Claude Code trong CLAUDE.md “luôn format code sau khi sửa”, “đừng bao giờ chạy rm -rf”, “chạy test trước khi báo xong”. Model sẽ làm theo… phần lớn thời gian. Hooks biến những lời dặn đó thành quy tắc chắc chắn: một lệnh shell (hoặc HTTP, prompt, agent) tự chạy tại đúng thời điểm trong vòng đời của Claude Code, có thể chặn hành động hoặc bắt Claude làm tiếp.

Hook hoạt động thế nào?

Một hook gồm ba phần:

  • Sự kiện (event): thời điểm hook chạy, ví dụ PreToolUse (trước khi dùng tool), Stop (khi Claude định dừng).
  • Matcher: bộ lọc, ví dụ chỉ khi tool là Bash, hoặc Edit|Write.
  • Handler: việc cần làm — chạy lệnh shell, gọi HTTP endpoint, gọi tool MCP, hỏi một model (prompt), hoặc sinh subagent kiểm tra.

Claude Code gửi dữ liệu sự kiện dạng JSON vào stdin của hook (gồm session_id, cwd, tool_name, tool_input…). Hook trả lời bằng exit code hoặc JSON trên stdout.

Các sự kiện hay dùng

Sự kiệnChạy khiChặn được?
SessionStartBắt đầu/tiếp tục phiênKhông — dùng để nạp thêm context
UserPromptSubmitBạn gửi promptCó
PreToolUseTrước khi Claude dùng toolCó — cho phép, từ chối, hỏi, hoặc sửa input
PostToolUseSau khi tool chạy xongKhông (tool đã chạy) — nhưng gửi phản hồi được
NotificationClaude Code gửi thông báo (vd chờ bạn duyệt)Không
Stop / SubagentStopClaude / subagent định kết thúc lượtCó — bắt làm tiếp
PreCompactTrước khi nén hội thoạiCó
TaskCompleted, TeammateIdleTask / teammate trong agent teamCó
SessionEndKết thúc phiênKhông

Ngoài ra còn nhiều sự kiện khác như ConfigChange, FileChanged, PermissionRequest, PreModelSwitch… — gõ /hooks trong Claude Code để xem hook đang cấu hình và nguồn của chúng.

Exit code — phần quan trọng nhất

Exit codeÝ nghĩa
0Thành công. Nếu stdout là một object JSON, các trường của nó được áp dụng.
2Lỗi chặn (với sự kiện chặn được). Nội dung stderr được gửi cho Claude làm lý do.
Khác (vd 1)Lỗi không chặn: hành động vẫn diễn ra, bạn chỉ thấy thông báo “hook error”.

Lỗi phổ biến nhất của người mới: dùng exit 1 để chặn — không chặn được. Luật bắt buộc phải dùng exit 2. Với Stop, exit 2 nghĩa là “chưa được dừng, làm tiếp”.

Cấu hình ở đâu?

Vị tríPhạm vi
~/.claude/settings.jsonMọi dự án trên máy bạn
.claude/settings.jsonDự án — commit vào repo để cả team dùng
.claude/settings.local.jsonDự án, chỉ máy bạn (gitignore)
Managed settingsToàn tổ chức
Plugin, frontmatter của skill/subagentKhi plugin bật / khi skill hoặc subagent chạy

Ví dụ 1: Tự format sau mỗi lần sửa file

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
      }]
    }]
  }
}

Hook đọc JSON từ stdin, lấy đường dẫn file vừa sửa và chạy Prettier. Claude không cần nhớ format nữa.

Ví dụ 2: Chặn lệnh rm

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "if": "Bash(rm *)",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
      }]
    }]
  }
}
#!/bin/bash
# .claude/hooks/block-rm.sh  (nhớ chmod +x)
echo "Không được xoá file bằng rm. Hãy hỏi người dùng trước." >&2
exit 2

Trường if dùng cú pháp của permission rule (Bash(git *), Edit(*.ts)) để lọc hẹp hơn matcher. Lưu ý: if chỉ là “best-effort”; muốn chặn tuyệt đối, dùng permission deny thay vì chỉ dựa vào hook.

Ví dụ 3: Không cho Claude dừng khi test còn đỏ

#!/bin/bash
# Hook cho sự kiện Stop
if ! npm test --silent > /tmp/test.log 2>&1; then
  echo "Test vẫn còn lỗi. Đọc /tmp/test.log và sửa trước khi kết thúc." >&2
  exit 2
fi
exit 0

Đây là cách biến “Definition of Done” thành quy tắc máy kiểm tra được.

Ví dụ 4: Từ chối có lý do bằng JSON

Thay vì exit 2, hook PreToolUse có thể in JSON để quyết định chi tiết hơn:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Không được ghi trực tiếp vào database production"
  }
}

permissionDecision nhận allow, deny, ask hoặc defer; có thể kèm updatedInput để sửa tham số trước khi chạy. JSON phải là nội dung duy nhất trên stdout — một dòng chào từ shell profile cũng đủ làm hỏng việc parse.

Các loại handler khác

  • http: POST JSON sự kiện tới một URL — hợp để ghi log tập trung hoặc gửi cảnh báo.
  • mcp_tool: gọi một tool của MCP server đã cấu hình.
  • prompt: nhờ một model đánh giá nhanh một lượt (mặc định timeout 30 giây).
  • agent: sinh subagent có Read/Grep/Glob để kiểm tra điều kiện (thử nghiệm).

Timeout mặc định của hook lệnh là 600 giây. Biến $CLAUDE_PROJECT_DIR trỏ tới thư mục gốc dự án — dùng nó để gọi script theo đường dẫn tuyệt đối.

Bảo mật và kiểm soát

  • Hook chạy lệnh tuỳ ý với quyền của bạn — đọc kỹ hook đi kèm plugin, skill, subagent trước khi bật.
  • Hook trong dự án tuân theo cơ chế tin cậy thư mục.
  • Tổ chức có thể dùng allowManagedHooksOnly để chỉ cho phép hook do quản trị cấp.
  • Tắt toàn bộ hook tạm thời: "disableAllHooks": true.
  • Gỡ lỗi: claude --debug hoặc /debug ghi đầy đủ stderr và output của hook.

Áp dụng thế nào?

  • Dev: Bắt đầu với hai hook rẻ mà lợi: format sau khi sửa file và chặn rm/git push --force.
  • Tech Lead: Đưa hook vào .claude/settings.json của repo, review như review CI config. Hook Stop chạy test + lint là cách rẻ nhất để giảm PR “AI làm xong nhưng build đỏ”.
  • PM: Mỗi quy tắc “team hay quên” (cập nhật changelog, không sửa file migration cũ…) là ứng viên cho hook. Biến checklist thành tự động thay vì nhắc trong mỗi buổi review.

Nguồn & tham khảo

  1. Hooks reference — Claude Code Docs