Bản tương tác
Bài có kèm một trang tương tác: sơ đồ, tình huống và session minh hoạ. Link mở được tạo mới mỗi lần bấm.
Tóm tắt nhanh: Lời dặn trong prompt chỉ là gợi ý, model có thể quên hoặc lệch. Guardrail (hàng rào an toàn) thật là hook chạy trong harness của Claude Code, kiểm từng tool call trước khi nó chạy và chặn bằng
exit 2. Bài này giải thích cơ chế đó, rồi mổ từng lớp chặn trong AgentKit: lớp nào chặn bằng code, lớp nào chỉ là lời dặn, cùng những chỗ hở bạn nên kiểm trước khi tin.
Khi agent có quyền đọc file, chạy shell, sửa code và mở PR, một dòng dặn trong prompt là chưa đủ. Cần một lớp kiểm tra đứng trước lúc tool chạy.
Công khai: mình là maintainer của AgentKit và có quan hệ affiliate: link AgentKit trong bài là link affiliate, mua qua link này bạn được giảm 30%.
Ghi chú: bản beta hiện tại của AgentKit (từ 3.0.0-beta.35, 21/10/2026) đã đổi format tên skill từ ak:<tên> sang ak-<tên>, nên lệnh trong bài đã cập nhật theo tên mới: Claude Code gõ /ak-plan, Codex gõ $ak-plan. Nếu bạn còn dùng bản cũ hơn, đổi ak- thành ak: là ra lệnh tương ứng.
AgentKit là tên mới của ClaudeKit. Phần cấu hình trong bài là ảnh chụp theo bản claudekit-engineer@2.19.1 lúc mình viết (tháng 6/2026), nên tên file (.ck.json, .ckignore), tên hook, key và biến môi trường (CK_*) được giữ nguyên như bản đó. Lúc rà lại với AgentKit 3.0.0-beta.1 (tháng 10/2026), lệnh đã chuyển sang tiền tố /ak:, .ck.json và CK_SIMPLIFY_DISABLED vẫn được đọc (tài liệu chuyển đổi có nhắc tuỳ chọn chuyển cấu hình sang config.yaml, nhưng hook trong bản cài vẫn đọc .ck.json), còn hook workflow-artifact-gate không có trong bản cài mới. Phần về gate này giữ lại để bạn hiểu cách kiểm artifact, nhưng hãy kiểm thư mục .claude/hooks/ trên máy trước khi bật. Mình sẽ giải thích cơ chế chung của Claude Code trước, rồi mới đi vào từng lớp chặn của AgentKit.
Một việc nhỏ, nhiều quyền quá rộng
Bạn giao cho agent một việc nhỏ: sửa phần kiểm tra dữ liệu đầu vào trong một module. Nó muốn chắc hơn nên quét thêm vài thư mục, mở một file cấu hình nhạy cảm, rồi tiện tay gom luôn vài chỗ dọn dẹp không liên quan vào cùng một PR.
Nhìn qua thì mọi thứ vẫn ổn, test vẫn đạt. Nhưng ngữ cảnh đã lẫn file rác, secret có thể đã đi vào transcript (toàn bộ bản ghi hội thoại giữa bạn và agent), còn việc duyệt PR thì bị loãng vì phần cần sửa và phần "tiện tay" nằm chung một chỗ.
So sánh hai trường hợp:
| Hành động của agent | Không có guardrail | Có guardrail |
|---|---|---|
| Glob quá rộng | Ngữ cảnh lẫn file rác | scout-block trả exit 2 |
Đọc .env | Secret vào bản ghi hội thoại | privacy-block hỏi người dùng |
| Dọn dẹp ngoài phạm vi | Duyệt PR bị loãng | Quy tắc và hard-gate nhắc quay lại việc chính |
| Test đạt nên làm tiếp | Thiếu bước kiểm phạm vi | simplify-gate hoặc chốt duyệt kiểm lại diff trước khi phát hành |
Agent không tự nhiên cẩn thận hơn. Chỉ là trước khi nó làm gì đó, có một lớp đứng ra hỏi: việc này có nên được phép chạy không?
Ý chính của cả bài: hook là lớp chặn thật. Guardrail không làm model thông minh hơn, nó kiểm tra hành động trước khi tool chạy. Quy tắc, hard-gate và guard skill là các lớp định hướng thêm, có ích nhưng vẫn phụ thuộc vào việc model hoặc người dùng có làm theo hay gọi tới hay không.
Guardrail là gì trong agentic coding
Guardrail là lớp đứng giữa agent và hành động: việc rủi ro thì chặn, việc đáng nghi thì cảnh báo. Nó giống lan can ở mép cầu thang: không làm bạn đi giỏi hơn, nhưng giảm khả năng một bước hụt thành tai nạn. Khác với lời dặn trong prompt, guardrail chạy ở tầng điều phối tool, không phụ thuộc model có "nhớ" hay không.
Phân biệt guardrail và lời dặn
| Guardrail | Lời dặn | |
|---|---|---|
| Chạy ở đâu | Trong harness, ngoài model | Trong ngữ cảnh, model đọc |
| Ép buộc | Code chặn thật | Model tự nguyện làm theo |
| Ngữ cảnh dài | Vẫn chạy | Dễ quên hoặc trượt |
| Model hiểu lệch | Vẫn chặn (nếu hook không crash) | Lệch theo model |
| Sửa đổi | Phải sửa code hoặc cấu hình | Sửa chữ là xong |
Guardrail hoạt động thế nào
Harness là lớp runtime bọc quanh model. Model chỉ quyết định "tôi muốn đọc file này" hay "tôi muốn chạy lệnh này". Harness mới là bên nhận tool call, quản quyền, gọi hook, cho tool chạy hoặc chặn, rồi trả kết quả lại cho model. Hook là một script nhỏ mà harness tự gọi ở các thời điểm cố định trong vòng đời một tool call (lúc nhận prompt, trước khi tool chạy, sau khi tool chạy) để xen vào kiểm tra. Vì mọi hành động thật đều đi qua harness, đây là chỗ gắn guardrail.
Đọc thêm phần nền: Harness Engineering là gì? của Duy /zuey/.
Hook nằm ở đâu trong vòng đời Claude Code
Hook không nằm trong model. Nó nằm trong vòng đời của Claude Code: settings.json quyết định hook nào được gọi; .ck.json cùng biến môi trường quyết định hook đó làm gì khi chạy. Kết quả hook trả về có thể cho chạy, chặn, chèn thêm ngữ cảnh, hoặc ghi trạng thái và file kết quả sau khi tool hay session kết thúc. Chi tiết về hook của AgentKit có thể xem thêm ở VividKit Guides.
Một tool call đi qua guardrail
Điểm mấu chốt: hook chạy trước tool trả về một exit code. exit 0 cho tool chạy, exit 2 chặn và đẩy lý do về cho model. Ba điểm chèn chính có tên kỹ thuật tương ứng: UserPromptSubmit (lúc nhận prompt), PreToolUse (trước tool), PostToolUse (sau tool). Riêng Stop và SubagentStop chạy khi session hoặc subagent kết thúc.
Exit code là con số một tiến trình trả về khi kết thúc. Theo tài liệu hook của Claude Code, Claude Code đọc con số đó để quyết định tool có chạy hay không:
| Exit code | Tool có chạy không | Chuyện gì xảy ra |
|---|---|---|
exit 0 | Có | Thành công. Nội dung stdout được đọc như kết quả JSON. |
exit 2 | Không, bị chặn | Bỏ stdout, đẩy nội dung stderr về cho model làm thông báo lỗi. |
exit 1 hoặc mã khác | Có | Lỗi nhưng không chặn. Lỗi được báo ra, tool vẫn chạy tiếp. |
Trong các mã thoát, chỉ exit 2 mới chặn thật. Hook muốn ép chính sách bằng mã thoát phải dùng đúng exit 2. Dùng nhầm exit 1 theo thói quen Unix thì guard tưởng đang bật mà thực ra vẫn mở. Cách chặn còn lại là exit 0 kèm JSON trên stdout: với PreToolUse, trả hookSpecificOutput.permissionDecision là "deny" cũng chặn tool, lý do ghi trong permissionDecisionReason (PreToolUse decision control).
Hai kiểu chặn
Chặn cứng bằng code. Hook trả exit 2, hoặc trả quyết định deny qua JSON, thì tool không chạy. Nhưng nếu hook crash, Claude Code vẫn cho tool chạy tiếp, gọi là fail-open. Một lỗi trong hook sẽ làm lớp chặn đó tắt âm thầm.
Chặn mềm bằng chữ. Thêm chữ vào ngữ cảnh (quy tắc, hard-gate) và model phải tự làm theo. Mạnh hay yếu tuỳ vào model.
Tách guardrail thành nhiều lớp đem lại mấy lợi ích:
- Giữ ngữ cảnh sạch và giữ secret ngoài bản ghi hội thoại.
- Giữ nhịp làm việc: lập kế hoạch, rút gọn, rồi duyệt.
- Chặn sớm trước khi rủi ro lan ra, khi agent định đọc
.env, quét quá rộng hoặc kéo phần dọn dẹp ngoài phạm vi vào PR. - Các lớp bù cho nhau: hook có thể fail-open, quy tắc có thể bị model bỏ qua, guard skill có thể không được gọi. Nhiều lớp giúp bớt phụ thuộc vào một điểm chặn duy nhất.
Bảy nhóm guardrail trong AgentKit
Sau một lần cài mới, lớp chặn truy cập file của AgentKit nằm ở các hook PreToolUse như scout-block và privacy-block. Nếu session Claude Code đang chạy ở chế độ bỏ qua hộp hỏi quyền, các hook này vẫn là lớp chặn chính. Nhưng hook của AgentKit là fail-open: crash hoặc bị tắt thì tool call có thể đi tiếp.
| Nhóm | Cơ chế | Ví dụ | Mức ép buộc |
|---|---|---|---|
| Chặn file và đường dẫn | PreToolUse | scout-block, privacy-block | Code, fail-open |
| Chặn sai bước | UserPromptSubmit | simplify-gate | Code |
| Thêm ngữ cảnh | UserPromptSubmit | dev-rules-reminder | Code |
| Giữ tên rõ nghĩa | Pre, Post, Stop | descriptive-name | Code, chỉ nhắc |
| Hard-gate trong skill | XML trong markdown | <HARD-GATE> | Lời dặn |
| Quy tắc dạng lời dặn | CLAUDE.md | review-audit | Lời dặn |
| Guard skill | Người dùng gọi | ak-security, ak-ship | Người dùng |
Mỗi nhóm có mục riêng ở Phần 2: chặn file và đường dẫn ở mục scout-block và privacy-block, chặn sai bước ở mục simplify-gate và workflow-artifact-gate, hard-gate ở mục hard-gate XML, thêm ngữ cảnh và quy tắc ở mục dev-rules-reminder, guard skill ở mục cuối của các lớp chặn. descriptive-name chỉ có trong bảng hook.
Chín tình huống quen thuộc
| Tình huống | Lớp xử lý |
|---|---|
Đọc node_modules/react/ | scout-block, exit 2 |
Đọc .env để kiểm key | privacy-block, exit 2 kèm hộp hỏi phê duyệt |
Glob **/*.ts ở thư mục gốc | scout-block, chặn mẫu quá rộng |
| Prompt đòi phát hành khi diff đã phình to | simplify-gate, nếu gate.enabled=true |
| Bắt đầu code khi chưa có kế hoạch hoặc chưa duyệt | HARD-GATE của ak-cook |
| Đổi ngưỡng mà người dùng đã chốt | Quy tắc review-audit: hỏi lại trước khi đổi |
| File mới có tên mơ hồ | descriptive-name, PreToolUse(Write) |
| Kế hoạch dùng link hoặc chữ sai định dạng | plan-format-kanban, PostToolUse(Edit/Write/MultiEdit) |
| Hai thành viên trong nhóm agent cùng sửa một file | Quy tắc team-coordination, chỉ trong Agent Team |
Settings và các lớp cấu hình
Đây là nơi kiểm xem hook có chạy hay không. Hai lớp cấu hình quyết định lớp chặn nào chạy: settings.json gắn hook vào vòng đời của Claude Code, còn .ck.json bật tắt từng hook và chỉnh ngưỡng.
# Phạm vi toàn cục
~/.claude/settings.json
~/.claude/.ck.json
~/.claude/hooks/*.cjs
# Phạm vi dự án
.claude/settings.json
.claude/.ck.json
.claude/hooks/*.cjssettings.json: hook nào chạy ở mốc nào
Đoạn dưới chỉ minh hoạ một lát cắt PreToolUse của bản cài mới: AgentKit cung cấp scout-block và privacy-block, rồi gắn chúng vào vòng đời để Claude Code gọi trước khi tool chạy. Cấu hình hook đầy đủ trên máy còn nhiều sự kiện khác; nơi cần xem là settings.json của Claude Code.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Glob|Grep|Read|Edit|Write",
"hooks": [
{ "type": "command", "command": "node \".claude/hooks/scout-block.cjs\"" },
{ "type": "command", "command": "node \".claude/hooks/privacy-block.cjs\"" }
]
}
]
}
}settings.json là bản đồ vòng đời: khi người dùng gửi prompt thì hook nào chạy, trước khi tool chạy thì hook nào được gọi, sau khi tool xong thì hook nào xử lý tiếp. Với bản cài toàn cục, CLI đổi lệnh dùng đường dẫn tương đối trong mẫu thành dạng node "$HOME/.claude/hooks/scout-block.cjs". Với bản cài theo dự án, đường dẫn thường giữ dạng .claude/hooks/....
Đọc trạng thái hook theo ba lớp
Đừng gom mọi thứ thành "mặc định bật" hay "mặc định tắt". Trạng thái của một hook có ba lớp khác nhau:
| Lớp | Nghĩa khi kiểm |
|---|---|
| Có file script | File .cjs nằm trong .claude/hooks/. Có file chưa có nghĩa là hook đang chạy. |
| Đã gắn | settings.json đã gắn script vào một sự kiện trong vòng đời của Claude Code. Chưa gắn thì Claude Code không tự gọi hook đó. |
| Cờ runtime | Khi hook đã được gọi, code bên trong đọc .ck.json, DEFAULT_CONFIG hoặc biến môi trường để quyết định có chạy tiếp không. Vài lớp chặn còn có công tắc con như privacyBlock hoặc simplify.gate.enabled. |
Bảng dưới là ảnh chụp danh sách hook theo bản claudekit-engineer@2.19.1. Cột cuối là cách kích hoạt từng hook để tự kiểm. Lúc mình viết bài, hai hook task-completed-handler và teammate-idle-handler đang được lên kế hoạch bỏ ở bản kế tiếp, nên đừng coi chúng là hook lâu dài.
| Hook | Sự kiện | Trạng thái sau khi cài | Cách kích hoạt để kiểm |
|---|---|---|---|
session-init | SessionStart | Đã gắn, cờ true. Chuẩn bị dự án và biến môi trường khi khởi động, resume, clear, compact | Mở, resume, clear hoặc compact một session (SessionStart(startup|resume|clear|compact)) |
usage-quota-cache-refresh | SessionStart, UserPromptSubmit, PostToolUse | Đã gắn, không đọc cờ riêng, cứ được gắn là chạy. Lưu cache lượng dùng | Mở session, gửi prompt mới, hoặc cập nhật Task/Todo |
simplify-gate | UserPromptSubmit | Script được gọi (cờ true), nhưng simplify.gate.enabled mặc định false nên chưa chặn | Gửi prompt có ý định ship, merge, pr, deploy, publish khi diff đủ lớn |
dev-rules-reminder | UserPromptSubmit | Đã gắn, cờ true. Chèn quy tắc phát triển và ngữ cảnh | Gửi prompt mới. Quy tắc được chèn theo TTL |
subagent-init | SubagentStart | Đã gắn, cờ true. Chèn ngữ cảnh cho subagent | Khởi động subagent qua Task hoặc luồng agent |
descriptive-name | PreToolUse(Write) | Đã gắn, cờ true. Nhắc đặt tên file, script rõ nghĩa | Để Claude định tạo file bằng Write |
scout-block | PreToolUse trên Bash, Glob, Grep, Read, Edit, Write | Đã gắn, cờ true. Chặn thư mục nặng và glob quá rộng | Để Claude đọc node_modules, dist hoặc glob quá rộng |
privacy-block | PreToolUse trên Bash, Glob, Grep, Read, Edit, Write | Đã gắn, cờ true, thêm privacyBlock là true. Chặn đường dẫn chứa secret, hỏi phê duyệt | Để Claude đọc .env, file key, đường dẫn chứa secret |
plan-format-kanban | PostToolUse(Edit/Write/MultiEdit) | Đã gắn, không đọc cờ riêng. Cảnh báo kế hoạch sai định dạng | Để Claude sửa hoặc ghi một file kế hoạch |
session-state | PostToolUse, SubagentStop, Stop | Đã gắn, cờ true. Ghi trạng thái session và việc | Cập nhật Task/Todo, kết thúc subagent hoặc kết thúc một lượt |
cook-after-plan-reminder | SubagentStop(Plan) | Đã gắn. Không có key riêng trong DEFAULT_CONFIG.hooks, nhưng isHookEnabled() chỉ tắt khi cờ là false, nên thiếu key vẫn bật | Để subagent Plan kết thúc |
workflow-artifact-gate | Chưa gắn | Cờ false. Phải tự bật: gắn hook và bật cờ, cấu hình gate | Gắn vào UserPromptSubmit hoặc PreToolUse(Bash), hoặc chạy script với --stage. Bản cài mới chưa tự kích hoạt chế độ hook |
task-completed-handler | TaskCompleted | Dùng cho Agent Teams khi một việc hoàn tất. Đang được lên kế hoạch bỏ | Hoàn tất một việc trong Agent Teams |
teammate-idle-handler | TeammateIdle | Dùng cho Agent Teams khi một thành viên rảnh việc. Đang được lên kế hoạch bỏ | Để một thành viên trong Agent Teams hết việc và chuyển sang chờ |
team-context-inject | Chưa gắn | Thiếu key nghĩa là bật. Chỉ có ý nghĩa khi lớp workflow hoặc nhóm agent gọi tới | Gắn vào SubagentStart, rồi khởi động subagent của nhóm. Script chỉ có tác dụng khi agent id thuộc nhóm |
usage-context-awareness | Chưa gắn | Cờ true. Lớp bọc cũ quanh hook làm mới cache lượng dùng, không có trong mẫu settings.json | Gắn vào sự kiện mong muốn, hoặc dùng hook làm mới cache đang gắn sẵn |
Điểm dễ sót: isHookEnabled() chỉ tắt khi hooks.<name> là false, key thiếu thường được xem là bật. Nhưng vài lớp chặn còn có công tắc riêng: privacy-block vẫn đọc key cũ privacyBlock=false, còn simplify-gate có simplify.gate.enabled và biến môi trường CK_SIMPLIFY_DISABLED=1.
Tự bật tắt và kiểm tra hook
Muốn chỉnh nhanh, sửa .ck.json ở phạm vi cần tác động. Cấu hình dự án được ưu tiên hơn cấu hình toàn cục khi key được đặt rõ; key thiếu thì kế thừa hoặc dùng giá trị mặc định. Với hook đã gắn trong settings.json, đặt false là tắt, đặt true là bật lại sau khi phạm vi cao hơn đã tắt. Tên hook cũng chính là key trong hooks.<name>.
// .claude/.ck.json hoặc ~/.claude/.ck.json
{
"hooks": {
"scout-block": false,
"privacy-block": true
},
"privacyBlock": true,
"simplify": {
"gate": {
"enabled": true
}
}
}| Muốn làm | Chỉnh ở đâu | Lưu ý |
|---|---|---|
| Tắt một hook đã gắn | .ck.json: {"hooks":{"scout-block":false}} | Script vẫn còn, nhưng lúc chạy sẽ cho qua. |
| Bật lại hook đã bị tắt ở cấu hình toàn cục | .ck.json của dự án: {"hooks":{"scout-block":true}} | Key đặt rõ ở dự án ghi đè cấu hình toàn cục. |
| Tắt lớp chặn secret | .ck.json: {"hooks":{"privacy-block":false}}, hoặc key cũ {"privacyBlock":false} | privacyBlock=false là key cũ nhưng vẫn có hiệu lực. |
Bật simplify-gate chặn thật | .ck.json: { "hooks": { "simplify-gate": true }, "simplify": { "gate": { "enabled": true } } } | Thiếu simplify.gate.enabled=true thì gate chưa bật chế độ chặn. |
Tắt simplify-gate theo session hoặc phạm vi | settings.json: { "env": { "CK_SIMPLIFY_DISABLED": "1" } } | Biến môi trường mạnh hơn cấu hình gate. |
Bật workflow-artifact-gate | settings.json: gắn vào UserPromptSubmit và PreToolUse(Bash); .ck.json: {"hooks":{"workflow-artifact-gate":true}} | Bản cài mới chưa gắn hook này, nên chỉ sửa .ck.json là chưa đủ. |
Muốn biết hook có được kích hoạt không, hãy nhìn sự kiện trong vòng đời chứ đừng nhìn tên file. Cùng một script chỉ chạy khi sự kiện và matcher trong settings.json khớp với hành động hiện tại. Hook chưa gắn thì cần gắn trước, hoặc gọi thẳng qua CLI nếu script hỗ trợ.
// ~/.claude/settings.json hoặc .claude/settings.json
{
"env": {
"CK_SIMPLIFY_DISABLED": "1"
}
}Đọc tiếp Phần 2: từng lớp chặn, chỗ hở và cách dùng: mổ từng lớp guardrail của AgentKit, lớp nào chặn bằng code, lớp nào chỉ là lời dặn.
Dùng bài này trong agent của bạn
Lấy API keyChép đoạn dưới, dán vào Claude Code, Codex hay agent bạn đang dùng. Agent đọc bài rồi đối chiếu với repo đang mở.
Đọc bài "Guardrail trong Claude Code và AgentKit (1/2): cơ chế hook và các lớp cấu hình" trên modernEngineer rồi áp dụng vào repo này.
1. Gọi tool content_get của MCP server modernengineer với locale "vi", slug "guard-rails-in-claude-code-and-claudekit". Nếu chưa nối MCP hoặc bị từ chối quyền, đọc thẳng trang https://modernengineer.dev/vi/library/guard-rails-in-claude-code-and-claudekit.
2. Tóm tắt các bước chính của bài, chỉ ra bước nào áp dụng được cho repo này.
3. Đề xuất thay đổi cụ thể và hỏi tôi trước khi sửa file.Nối MCP cho Claude Code (một lần)
claude mcp add --transport http modernengineer \
https://api.modernengineer.dev/mcp \
--header "Authorization: Bearer <API key>"Bài mở cho mọi người: chưa nối MCP thì agent vẫn đọc được thẳng từ link bài.
- Phiên bản
- v1.0.0
- Cập nhật
- 09/06/2026
- Giấy phép
- Bảo lưu mọi quyền
- Slug
- guard-rails-in-claude-code-and-claudekit


