Bạn và team thống nhất: mọi TS file phải format Prettier trước commit. Bạn viết trong CLAUDE.md:
- Giải thích hook là gì và nó chèn vào luồng Claude Code ở đâu
- Phân biệt PreToolUse và PostToolUse hook, và khi nào dùng mỗi loại
- Nhận ra 3 vị trí lưu cấu hình hook (global / project shared / project local)
- Đọc hiểu structure JSON của một hook configuration
- Nhận diện 6 ứng dụng thực tế của hooks cho team
Hook là gì?
Hook = command shell bạn định nghĩa, chạy tự động trước hoặc sau khi Claude gọi tool.
Không phải code Claude viết. Không phải prompt. Là hệ thống chạy.
Luồng bình thường (không hook)
Luồng có hook
Hook chèn vào giữa — cung cấp cho bạn điểm can thiệp deterministic.
┌──────────────────────────────────────────────────────────┐ │ │ │ Bạn gõ prompt │ │ │ │ │ ▼ │ │ LM quyết định dùng tool Edit │ │ │ │ │ ▼ │ │ ┌──────────── PreToolUse HOOK ─────────────┐ │ │ │ Hook chạy trước khi Edit │ │ │ │ → Hook có thể BLOCK, cho phép, hoặc log │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ (nếu hook cho phép) │ │ Claude Code thực thi Edit │ │ │ │ │ ▼ │ │ ┌──────────── PostToolUse HOOK ────────────┐ │ │ │ Hook chạy sau khi Edit │ │ │ │ → Format file, chạy typecheck, lint... │ │ │ │ → Feedback quay lại Claude │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ Kết quả + feedback trả LM tiếp tục │ │ │ └──────────────────────────────────────────────────────────┘
Hai loại hook căn bản
PreToolUse — Chặn trước
Chạy trước khi tool được execute. Có thể:
Ứng dụng điển hình:
PostToolUse — Phản ứng sau
Chạy sau khi tool đã execute. Không thể block (đã xong). Nhưng có thể:
Ứng dụng điển hình:
- ✅ Cho phép tool chạy bình thường (exit code 0)
- 🚫 Block tool (exit code 2) + gửi error message quay lại Claude
- Chặn Claude đọc file sensitive (.env, secrets/)
- Block command nguy hiểm (rm -rf /, destructive migrations)
- Enforce naming convention (Claude định tạo file với tên sai → block)
- Require confirmation cho tool nhất định
- ✅ Chạy follow-up action (format file vừa edit, chạy test)
- ✅ Cung cấp feedback về output Claude vừa làm
- ✅ Log / audit để review sau
- Auto-format file sau Edit
- Run typecheck, push error quay lại Claude để fix
- Update changelog, audit log
- Trigger tests in watch mode
Vị trí cấu hình hook
Hook định nghĩa trong settings.json. Có 3 vị trí, tương tự CLAUDE.md:
Khi nào dùng từng vị trí?
| Rule | Đặt ở đâu | Lý do |
|---|---|---|
| Auto-format TS khi edit | project/.claude/settings.json | Team convention |
| Block đọc .env | project/.claude/settings.json | Team security |
| Run typecheck sau edit | project/.claude/settings.json | Team quality |
| Log mọi tool call của Claude vào file cá nhân để audit | .claude/settings.local.json | Debug cá nhân |
| Không cho Claude rm trong mọi project | ~/.claude/settings.json | Safety cá nhân |
┌──────────────────────────────────────────────────────────┐ │ │ │ 1. ~/.claude/settings.json │ │ → Global, áp dụng MỌI project │ │ → Dùng cho habit cá nhân │ │ │ │ 2. .claude/settings.json (trong project) │ │ → Commit Git, chia sẻ team │ │ → Rule team cần enforce │ │ │ │ 3. .claude/settings.local.json (trong project) │ │ → Gitignore — không commit │ │ → Preference cá nhân cho project này │ │ │ └──────────────────────────────────────────────────────────┘
Structure của hook config
Shape tổng quát
Giải thích từng field
- PreToolUse / PostToolUse: loại hook
- matcher: pattern tên tool sẽ trigger hook. Hỗ trợ:
- Tên cụ thể: "Edit" → chỉ trigger khi tool Edit
- OR: "Edit|Write|MultiEdit" → bất kỳ tool edit
- Wildcard: "*" → mọi tool
- hooks[].type: hiện tại chỉ "command"
- hooks[].command: shell command Claude Code chạy
{
"hooks": {
"PreToolUse": [
{
"matcher": "<pattern>",
"hooks": [
{
"type": "command",
"command": "<shell command>"
}
]
}
],
"PostToolUse": [
{
"matcher": "<pattern>",
"hooks": [
{
"type": "command",
"command": "<shell command>"
}
]
}
]
}
}Ví dụ cấu hình 1: PreToolUse
Chặn Claude đọc file .env:
Chi tiết implement ở Bài 4.14.
- Matcher Read|Grep — cả 2 tool có thể đọc file
- Command chạy một Node script
- Script check path file có chứa .env → exit 2 (block) hoặc exit 0 (allow)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Grep",
"hooks": [
{
"type": "command",
"command": "node /absolute/path/to/hooks/block-env.js"
}
]
}
]
}
}Ví dụ cấu hình 2: PostToolUse
Format file sau khi Claude edit TS:
- Matcher: bất kỳ edit/write operation
- Script: chạy Prettier + tsc --noEmit, nếu có error → feedback lại Claude
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "/usr/bin/env node /absolute/path/hooks/format-and-typecheck.js"
}
]
}
]
}
}Dữ liệu hook nhận (stdin)
Khi hook chạy, Claude Code push JSON vào stdin của command. Command đọc, parse, xử lý.
Ví dụ JSON cho PreToolUse trên Read tool
Hook script có thể:
{
"session_id": "2d6a1e4d-6abc-...",
"transcript_path": "/Users/john/.claude/transcripts/2d6a...",
"hook_event_name": "PreToolUse",
"tool_name": "Read",
"tool_input": {
"file_path": "/Users/john/Projects/app/.env"
}
}Ví dụ JSON cho PreToolUse trên Read tool
Chi tiết JSON shape cho mỗi hook event → Bài 4.13.
const input = JSON.parse(await readStdin());
if (input.tool_input.file_path.includes('.env')) {
console.error("Blocked: cannot read .env files");
process.exit(2); // 2 = BLOCK (PreToolUse only)
}
process.exit(0); // 0 = ALLOW6 ứng dụng thực tế của hooks
1. Code formatting tự động
Claude edit xong, Prettier format. Code luôn consistent.
2. Type checking + feedback
Edit → PostToolUse: prettier --write <file>2. Type checking + feedback
Nếu error, Claude nhận feedback, tự fix. Bạn không phải babysit.
3. Access control
Edit → PostToolUse: tsc --noEmit, nếu error → stderr3. Access control
Claude không bao giờ đọc được .env, /secrets/, etc.
4. Running tests on file changes
Read/Grep → PreToolUse: block nếu path chứa pattern sensitive4. Running tests on file changes
Test chạy ngay, fail sớm.
5. Linting / convention enforcement
Edit → PostToolUse: nếu file X.ts → chạy X.test.ts5. Linting / convention enforcement
Claude auto-fix lint issue dựa trên feedback.
6. Logging / audit
Edit → PostToolUse: eslint, nếu warning → feedback6. Logging / audit
Review sau đó: Claude đã chạy gì, khi nào.
Bash → PostToolUse: log mọi command vào audit.logVí dụ thực chiến: Team setup hook đầu tiên
Tình huống
Team 6 dev, stack TypeScript + React. Team muốn 2 rule enforce:
Setup
Tạo project/.claude/hooks/ chứa script:
hooks/block-env.js:
hooks/typecheck.js:
- Không ai (Claude) được đọc .env
- Mọi file TS edit xong phải pass tsc --noEmit
#!/usr/bin/env node
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const input = JSON.parse(Buffer.concat(chunks).toString());
const path = input.tool_input?.file_path || input.tool_input?.path || '';
if (path.includes('.env')) {
console.error("Cannot access .env files. Use env vars at runtime.");
process.exit(2);
}
process.exit(0);Setup
.claude/settings.json:
#!/usr/bin/env node
const { execSync } = require('child_process');
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const input = JSON.parse(Buffer.concat(chunks).toString());
const path = input.tool_input?.file_path || '';
if (!path.endsWith('.ts') && !path.endsWith('.tsx')) process.exit(0);
try {
execSync('npx tsc --noEmit', { stdio: 'pipe' });
process.exit(0);
} catch (err) {
console.error(err.stdout.toString());
process.exit(2);
}Ví dụ thực chiến: Team setup hook đầu tiên (tiếp)
Kết quả
- Claude không bao giờ đọc .env trong 100+ session
- Type error bắt sớm, không lọt vào commit
- PR quality cao hơn nhanh, review human giảm
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Grep",
"hooks": [{ "type": "command", "command": "node $PWD/.claude/hooks/block-env.js" }]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "node $PWD/.claude/hooks/typecheck.js" }]
}
]
}
}Case studies theo ngành
🏥 Health tech — Enforce HIPAA boundary
Setup: PreToolUse hook block nếu Claude định write file ngoài phi-vault/ với PHI data pattern.
💰 Fintech — Audit log bắt buộc
Setup: PostToolUse hook log mọi Bash/Edit với session ID + timestamp vào append-only log.
🎓 EdTech — Student code safety
Setup: PreToolUse block rm, sudo, curl cho student env.
📣 Content team — Style guide enforce
Setup: PostToolUse cho file .md: chạy style checker, flag "corporate AI" phrase.
🛠️ Platform — Cost control
Setup: PreToolUse block MCP call đến service đắt tiền nếu usage quota ngày đã vượt.
- Kết quả: Zero PHI leak event trong 6 tháng.
- Kết quả: SOC 2 audit pass clean, có evidence mọi change.
- Kết quả: Zero incident system damage trong 500 student.
- Kết quả: Output content consistency với brand voice 95%.
- Kết quả: AI cost predictable, không bill sốc cuối tháng.
Anti-patterns
❌ Hook làm task chậm (>30s)
Biểu hiện: PostToolUse chạy full test suite sau mỗi edit → mỗi edit tốn 45 giây.
Hậu quả: Claude session chậm như rùa, UX tệ.
Cách đúng: Chỉ chạy test cho file đã edit, hoặc chạy test song song (background).
❌ Hook lỗi silent
Biểu hiện: Hook crash nhưng exit 0 → Claude tưởng OK.
Cách đúng: Hook exit code chuẩn, log error rõ ràng.
❌ Hook cần network call mỗi lần
Biểu hiện: PostToolUse hook upload mọi edit lên server → chậm + tốn bandwidth.
Cách đúng: Batch upload, or queue async.
❌ Hook chặn false positive
Biểu hiện: Block mọi file có substring "env" → block luôn environment.ts (là public).
Cách đúng: Pattern precise: /\.env(\.|$)/ thay vì /env/.
❌ Không test hook
Biểu hiện: viết hook, deploy team, hôm sau mọi người kêu Claude Code lag/crash.
Cách đúng: Test hook cá nhân 1-2 ngày trước khi push team.
Mẹo nâng cao
Mẹo 1: Debug hook bằng log-only hook
Muốn xem JSON Claude Code push vào stdin? Tạo hook log-only:
Trigger một tool call → xem /tmp/claude-hook.log. Từ đó biết structure để viết hook thật.
Chi tiết Bài 4.17.
Mẹo 2: Chia hook theo loại tool
Thay vì 1 hook monster, có nhiều hook nhỏ:
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "jq . > /tmp/claude-hook.log" }]
}
]
}
}Mẹo 2: Chia hook theo loại tool
Modularity, dễ maintain.
Mẹo 3: Hook trong Docker (isolation)
Hook chạy trong container → an toàn hơn, consistent giữa dev. Advanced setup nhưng đáng cho team lớn.
Mẹo 4: Giới hạn hook bằng /hooks
Nội bộ Claude Code có command /hooks — xem hook đang active, bật/tắt tạm thời khi debug.
{
"hooks": {
"PostToolUse": [
{ "matcher": "Edit|Write", "hooks": [...] }, // format
{ "matcher": "Bash", "hooks": [...] }, // log
{ "matcher": "Edit", "hooks": [...] } // typecheck
]
}
}Áp dụng ngay
Bài tập 1: Tạo hook log-only (15 phút)
Bước 1: Tạo .claude/settings.json trong project:
Hoặc nếu không có jq:
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "jq . > /tmp/claude-hook.json"
}
]
}
]
}
}Bài tập 1: Tạo hook log-only (15 phút)
Bước 2: Restart Claude Code.
Bước 3: Prompt task đơn giản: "Tạo file hello.txt với nội dung 'test'"
Bước 4: Check /tmp/claude-hook.json — JSON trông như thế nào?
Ghi:
Bài tập 2 (optional, 15 phút): Lên danh sách hook team cần
Brainstorm: team bạn có rule nào đang dùng CLAUDE.md để enforce mà hay bị Claude quên? Liệt kê 3-5 rule:
Mỗi rule, quyết định:
Preview cho Bài 4.13 (Định nghĩa hook) và 4.14 (Implement).
- hook_event_name: ____________
- tool_name: ____________
- tool_input có fields gì: ____________
- tool_response có gì: ____________
- ____________
- ____________
- ____________
- PreToolUse hay PostToolUse?
- Matcher tool nào?
- Command làm gì?
"command": "cat > /tmp/claude-hook.json"Tóm tắt bài học
🎯 Hook = command shell chạy tự động trước (Pre) hoặc sau (Post) tool call.
🎯 PreToolUse có thể block (exit 2), PostToolUse chỉ react.
🎯 3 vị trí config — global (cá nhân habit), project (team shared), local (cá nhân project-specific).
🎯 Structure JSON — hooks.{PreToolUse|PostToolUse}[].matcher + hooks[].hooks[].command.
🎯 Hook = deterministic enforcement — không dựa vào Claude nhớ, hệ thống làm.
- Claude Code hooks docs
- Anthropic security for hooks
- Bài 4.13 — Định nghĩa hook (chi tiết 4 bước)
- Bài 4.14 — Implement hook đầu tiên