IzziAI
TutorialJul 7, 20268 min read

Hooks — Lớp kiểm soát: guardrail tất định cho agent

Hooks là gì, các sự kiện vòng đời, cách cấu hình matcher và exit code, ví dụ chặn lệnh nguy hiểm, và vì sao hooks mới là 'guardrail thật' cho AI.

Izzi API Team
Engineering & DevRel
agent-dev-kitclaudeai-agent
Hooks — Lớp kiểm soát: guardrail tất định cho agent

Hooks — Lớp kiểm soát: guardrail tất định cho agent

Suốt hai bài trước, ta liên tục gặp một câu cảnh báo: CLAUDE.md và Skills là chỉ dẫn — Claude cố tuân theo nhưng không đảm bảo tuyệt đối. Vậy khi bạn cần một điều chắc chắn xảy ra — luôn format code sau khi sửa, luôn quét secret trước khi commit, tuyệt đối chặn lệnh xóa dữ liệu — thì dựa vào đâu?

Câu trả lời là Hooks. Đây là lớp biến "mong muốn" thành "bảo đảm".

Hook là gì: điểm khác biệt cốt lõi

Hook là một lệnh hoặc script do bạn định nghĩa, chạy tất định (deterministic) tại những điểm cố định trong vòng đời tool và hội thoại của Claude Code. Điểm mấu chốt: mô hình KHÔNG quyết định hook có chạy hay không — khi sự kiện xảy ra và điều kiện khớp, hook luôn chạy (Claude Docs — Hooks guide).

Hãy so sánh:

  • Viết "luôn chạy test trước khi commit" vào CLAUDE.md → Claude thường nhớ, đôi khi quên.
  • Đặt một hook chạy test → nó luôn chạy, không phụ thuộc tâm trạng của mô hình.

Đây là lý do Anthropic xếp hooks (cùng với permissions/managed settings) vào nhóm "guardrail thật", tách khỏi nhóm "chỉ dẫn" (Anthropic — Steering Claude Code). Hãy nghĩ về hooks như Git hooks cho agent của bạn.

Ngoài lệnh shell, một hook handler còn có thể là HTTP endpoint, MCP tool, một prompt gửi tới mô hình, hoặc một agent kiểm tra (Claude Docs — Hooks).

Các sự kiện hook (hook events)

Hook gắn vào các sự kiện đặt tên kiểu PascalCase. Bộ "kinh điển" bạn sẽ gặp nhiều nhất:

Sự kiệnKích hoạt khiChặn được?
PreToolUseNgay trước khi một tool chạy✅ (chặn tool)
PostToolUseSau khi tool trả kết quả❌ (việc đã xảy ra)
UserPromptSubmitKhi người dùng gửi prompt✅ (từ chối prompt)
SessionStartĐầu phiên— (khởi tạo)
SessionEndCuối phiên
StopKhi Claude định dừng lượt✅ (ngăn dừng)
SubagentStopKhi một subagent dừng
NotificationKhi Claude gửi thông báo
PreCompactTrước khi nén ngữ cảnh

Tài liệu hiện tại còn liệt kê thêm nhiều sự kiện khác (danh sách mở rộng theo phiên bản), nên khi triển khai hãy kiểm tra đúng phiên bản Claude Code bạn dùng (Claude Docs — Hooks).

Có thể nhóm theo ba "nhịp": một lần mỗi phiên (SessionStart/SessionEnd), một lần mỗi lượt (UserPromptSubmit/Stop), và mỗi lần gọi tool trong vòng lặp agentic (PreToolUse/PostToolUse).

Cấu hình hook ở đâu

Hook được khai báo trong file JSON settings, với cấu trúc ba tầng lồng nhau: sự kiện → nhóm matcher → danh sách handler. Vị trí file quyết định phạm vi (Claude Docs — Hooks):

  • ~/.claude/settings.json — mọi dự án (máy cá nhân).
  • .claude/settings.json — một dự án, commit được vào repo (chia sẻ cho team).
  • .claude/settings.local.json — một dự án, gitignored (riêng bạn).
  • Managed policy settings — toàn tổ chức (do admin đặt).
  • Trong plugin: hooks/hooks.json.

Cấu trúc điển hình:

JSON
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/path/to/lint-check.sh" }
        ]
      }
    ]
  }
}

Matcher: cẩn thận với regex

matcher quyết định hook chạy cho tool nào:

  • "*", "", hoặc bỏ trống = khớp tất cả.
  • Chỉ gồm chữ/số/_/- (và |, , để liệt kê) = so khớp chuỗi chính xác.
  • Có ký tự khác = regex JavaScript không neo (unanchored).

Cạm bẫy kinh điển: Edit (dạng regex) sẽ khớp cả NotebookEdit. Muốn khớp trọn vẹn, bọc ^...$. Với tool MCP, matcher lọc theo tên dạng mcp__<server>__.*.

Input, output và exit code

Claude gửi cho hook một JSON mô tả ngữ cảnh (qua stdin với command, hoặc body POST với http). Các trường chung gồm session_id, transcript_path, cwd, permission_mode, hook_event_name; sự kiện tool có thêm tool_name, tool_input (Claude Docs — Hooks).

Exit code là cách điều khiển đơn giản nhất:

  • exit 0 = thành công. Nếu hook in JSON ra stdout, Claude sẽ đọc JSON đó.
  • exit 2 = lỗi chặn (blocking). stdout bị bỏ qua; stderr được đưa lại cho Claude. Hệ quả tùy sự kiện: PreToolUse chặn tool call, UserPromptSubmit từ chối prompt, Stop ngăn Claude dừng... (còn PostToolUse/Notification thì không chặn được vì việc đã xảy ra).
  • Exit code khác = lỗi không chặn.

Muốn điều khiển tinh vi hơn exit code, hook có thể in JSON với các trường như continue, systemMessage, suppressOutput, decision/reason. Riêng PreToolUse hỗ trợ hookSpecificOutput.permissionDecision nhận allow/deny/ask/defer — cho phép "hỏi lại người dùng" trước khi tool chạy.

Hai ví dụ thực tế

(a) Tự lint/format sau khi sửa filePostToolUse khớp Edit|Write, gọi script format. Không cần chặn gì, chỉ dọn dẹp sau mỗi lần sửa.

(b) Chặn lệnh nguy hiểmPreToolUse khớp Bash, script đọc lệnh từ JSON và chặn nếu thấy rm:

Bash
#!/bin/bash
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
  echo "Bị chặn: lệnh rm không được phép" >&2
  exit 2
fi
exit 0

Đây chính là ví dụ "chặn rm -rf" trong sơ đồ bộ công cụ — và nó chắc chắn chặn, vì hook là tất định.

Best practices

  • Giữ hook đầu phiên/đầu lượt nhanh. UserPromptSubmit có timeout (khoảng 30 giây) và làm nghẽn việc xử lý prompt — đừng làm việc nặng ở đây.
  • Việc dài thì chạy nền. Với type: "command", đặt "async": true để Claude làm tiếp trong lúc hook (test suite, deploy) chạy nền.
  • Dùng biến đường dẫn. ${CLAUDE_PROJECT_DIR}${CLAUDE_PLUGIN_ROOT} giúp script không phụ thuộc thư mục hiện hành.
  • Đặt đúng chỗ. Ngữ cảnh tĩnh nên ở CLAUDE.md; hook dành cho hành động theo sự kiện.

Rủi ro bảo mật — đọc kỹ phần này

Đây là lớp quyền lực nhất và nguy hiểm nhất. Tài liệu chính thức ghi rõ: hook chạy lệnh shell tùy ý với toàn quyền của người dùng (Claude Docs — Hooks). Một hook độc hại hoặc viết ẩu có thể xóa file, rò rỉ dữ liệu, hoặc phá hệ thống.

Nguyên tắc an toàn (theo tài liệu):

  • Validate và sanitize mọi input trước khi dùng.
  • Luôn quote biến: "$VAR", không để $VAR trần.
  • Chặn path traversal (..), dùng đường dẫn tuyệt đối.
  • Tránh đụng file nhạy cảm: .env, .git/, khóa/credential.
  • Ở cấp tổ chức, admin có thể ép chỉ cho phép hook đã duyệt chạy (managed hooks).

Với doanh nghiệp: hãy đối xử với hook như code hạ tầng — review, versioning, và giới hạn nguồn tin cậy, giống như bạn làm với Git hooks hay CI/CD.

Vài hiểu lầm cần tránh

  • "Hook là gợi ý cho model." — Sai. Hook là bảo đảm tất định, khác hẳn skill/prompt.
  • "exit 2 luôn chặn được." — Sai. Hệ quả tùy sự kiện; PostToolUse/Notification không chặn được.
  • "matcher Edit chỉ khớp Edit." — Sai. Đó là regex không neo, khớp cả NotebookEdit.

Tóm tắt & bước tiếp theo

Hooks là lớp kiểm soát: chạy tất định theo sự kiện vòng đời, cấu hình qua JSON settings (phạm vi tùy vị trí file), điều khiển bằng exit code (đặc biệt exit 2 để chặn), và là "guardrail thật" duy nhất bên cạnh permissions. Đổi lại quyền lực đó là trách nhiệm bảo mật cao.

Cho tới giờ, ta vẫn nói về một agent với bộ nhớ, kỹ năng và kiểm soát. Nhưng một agent không nên ôm mọi việc — giống một công ty cần nhiều vai. Bài 4 giới thiệu Subagents: chia việc cho một đội trợ lý AI, mỗi cái có ngữ cảnh và quyền riêng.

Nguồn tham khảo

  • Claude Docs — Automate actions with hooks (hooks guide): https://docs.claude.com/en/docs/claude-code/hooks-guide
  • Claude Docs — Hooks reference: https://docs.claude.com/en/docs/claude-code/hooks
  • Anthropic — How to configure hooks: https://claude.com/blog/how-to-configure-hooks
  • Claude Docs — Intercept and control agent behavior with hooks (Agent SDK): https://code.claude.com/docs/en/agent-sdk/hooks
  • Anthropic — Steering Claude Code (chỉ dẫn vs guardrail): https://claude.com/blog/steering-claude-code-skills-hooks-rules-subagents-and-more

Bài 3/7 trong chuỗi "Bộ công cụ phát triển Agent với Claude". Nội dung diễn giải lại từ tài liệu gốc của Anthropic. Trước ← Bài 2: Agent Skills · Tiếp theo → Bài 4: Subagents.

Sẵn sàng bắt đầu?

Truy cập 38+ model AI qua một API duy nhất. Gói miễn phí — không cần thẻ tín dụng.

MORE

Bài viết liên quan