Hooks
Bài 9 — Hooks
#Vấn đề: bạn muốn kiểm soát hành vi của agent mà không cần ngồi canh
Bạn đang dùng Claude Code hàng ngày. Agent đọc file, sửa code, chạy terminal — tất cả tự động. Nhưng có những thứ bạn bắt buộc phải kiểm soát:
- Mỗi lần agent sửa file
.ts, bạn muốn ESLint chạy ngay lập tức để đảm bảo code style. - Agent không được phép chạy
rm -rfhayDROP TABLE— bất kể prompt nào. - Khi session bắt đầu, bạn muốn load environment variables hoặc kiểm tra branch hiện tại.
Làm thủ công? Mỗi lần ngồi canh agent rồi gõ lệnh lint, kiểm tra output? Không khả thi. Bạn cần một cơ chế tự động phản ứng trước và sau mỗi hành động của agent.
Đó chính là Hooks.
#Hooks là gì?
Hooks là các shell command chạy tự động khi một event xảy ra trong lifecycle của Claude Code. Bạn đăng ký một command gắn với một event, Claude Code sẽ chạy command đó vào đúng thời điểm — trước khi tool execute, sau khi file bị sửa, khi session bắt đầu, v.v.
Think of it như event listener trong JavaScript:
// Tưởng tượng thế này (pseudo-code)
claudeCode.on("PostToolUse", (event) => {
if (event.tool === "Write" && event.file.endsWith(".ts")) {
exec("npx eslint --fix " + event.file);
}
});Thực tế, bạn không viết JavaScript — bạn cấu hình trong settings.json bằng JSON.
#Lifecycle events
Claude Code cung cấp hơn 30 hook events, được tổ chức theo lifecycle:
Per Session — chạy một lần khi session bắt đầu/kết thúc:
SessionStart— khi bạn mở Claude CodeSessionEnd— khi thoátSetup— khi cấu hình được load
Per Turn — chạy mỗi lần user gửi prompt:
UserPromptSubmit— khi bạn gửi tin nhắnStop— khi agent hoàn thành response
Per Tool Call — chạy trước/sau mỗi lần agent gọi tool:
PreToolUse— trước khi tool thực thi (có thể chặn)PostToolUse— sau khi tool thực thi xongPostToolUseFailure— khi tool bị lỗiPostToolBatch— sau một batch tool callsPermissionRequest— khi tool yêu cầu quyềnPermissionDenied— khi quyền bị từ chốiSubagentStart/SubagentStop— lifecycle của sub-agentTaskCreated/TaskCompleted— lifecycle của task
Standalone — các event độc lập:
Notification— khi có notificationInstructionsLoaded— khi CLAUDE.md được loadConfigChange— khi cấu hình thay đổiFileChanged— khi file trên disk thay đổiPreCompact/PostCompact— trước/sau khi context được compact
#Cấu hình hooks
Hooks được cấu hình trong settings.json. Có 3 vị trí:
| File | Phạm vi |
|---|---|
~/.claude/settings.json | Toàn cục — mọi dự án |
.claude/settings.json | Dự án — commit vào git, chia sẻ với team |
.claude/settings.local.json | Dự án — chỉ local, không commit |
Cấu trúc cơ bản:
{
"hooks": {
"<EventName>": [
{
"matcher": "<pattern>",
"hooks": [
{
"type": "command",
"command": "your-shell-command"
}
]
}
]
}
}Trong đó:
<EventName>: tên event (ví dụ:PreToolUse,PostToolUse)matcher:"*"để match mọi thứ, hoặc một string/regex cụ thểtype: loại handler —command(shell),http,mcp_tool,prompt, hoặcagentcommand: lệnh shell sẽ chạy
#Ví dụ thực tế
#1. Auto-lint sau khi sửa file TypeScript
Đây là hook phổ biến nhất. Mỗi lần agent sửa file .ts hoặc .tsx, chạy ESLint tự động:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.(ts|tsx)$'; then npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>&1 || true; fi"
}
]
}
]
}
}Agent vừa tạo/sửa file → hook chạy ESLint → output quay lại context của agent → agent tự fix nếu có lỗi. Bạn không cần làm gì.
#2. Chặn lệnh nguy hiểm (PreToolUse)
Đây là ví dụ quan trọng nhất. Dùng PreToolUse để block trước khi lệnh chạy:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE '(rm -rf|DROP TABLE|git push --force)' && exit 2 || exit 0"
}
]
}
]
}
}exit 0 = cho phép chạy. exit 2 = chặn hoàn toàn — tool sẽ không thực thi. Cơ chế này cực kỳ mạnh: bạn có thể enforce bất kỳ rule nào mà không cần trust agent.
#3. Tự động load environment khi session bắt đầu
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "source ${CLAUDE_PROJECT_DIR}/.env.local 2>/dev/null; echo 'Environment loaded.'"
}
]
}
]
}
}${CLAUDE_PROJECT_DIR} là placeholder tự động resolve thành đường dẫn project.
#4. Log mọi tool call để audit
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"$(date -Iseconds) | Tool: $CLAUDE_TOOL_NAME | Input: $CLAUDE_TOOL_INPUT\" >> ${CLAUDE_PROJECT_DIR}/.claude-audit.log"
}
]
}
]
}
}#Handler types
Ngoài command (shell), bạn có thể dùng các loại handler khác:
| Type | Khi nào dùng |
|---|---|
command | Chạy shell script — phổ biến nhất |
http | Gọi webhook (gửi Slack notification, trigger CI) |
mcp_tool | Gọi một MCP tool khác |
prompt | Inject prompt vào context của agent |
agent | Spawn một sub-agent xử lý riêng |
⚠️ Cẩn thận: matcher quá rộng
Dùng
matcher: "*"trênPreToolUsehoặcPostToolUsesẽ chạy hook cho mọi tool call — kể cảRead,Glob,Grep. Điều này làm chậm agent đáng kể vì mỗi lần đọc file cũng trigger hook. Hãy matcher cụ thể:"Write","Bash","Edit"thay vì"*".
⚠️ Cẩn thận: exit code quyết định hành vi
Trong
PreToolUse:exit 0= cho phép,exit 2= chặn. Bất kỳ exit code nào khác cũng được coi là lỗi hệ thống. Đảm bảo script của bạn luôn exit đúng code. Dùng|| truenếu command có thể fail mà bạn không muốn block.
#Anti-pattern: không dùng hooks cho kiểm tra an toàn
Nhiều team "trust" agent hoàn toàn — không có hook nào kiểm soát. Đây là rủi ro lớn. Agent có thể:
- Chạy
rm -rf /nếu prompt bị injection - Force push lên main
- Xóa database production
Hooks là lớp phòng thủ cuối cùng. Ngay cả khi agent bị trick, hook PreToolUse vẫn chặn được lệnh nguy hiểm. Hãy coi hooks như firewall — bạn không tắt firewall chỉ vì "tin tưởng network nội bộ".
Một rule đơn giản: ít nhất có một PreToolUse hook chặn các lệnh destructive trong mọi dự án.
#Tổng kết
Hooks cho phép bạn inject logic tùy chỉnh vào lifecycle của Claude Code — tự động lint, chặn lệnh nguy hiểm, load environment, audit tool calls. Cấu hình trong settings.json với matcher và handler. PreToolUse có thể block hoàn toàn tool execution bằng exit 2.
Điểm mấu chốt: hooks là lớp phòng thủ bắt buộc, không phải optional. Mọi dự án nghiêm túc đều cần ít nhất một hook an toàn.
Bài tiếp theo: MCP (Model Context Protocol) — kết nối Claude Code với GitHub, database, Slack và các công cụ bên ngoài. Bạn sẽ biến Claude Code thành trung tâm điều khiển toàn bộ workflow.