Giới thiệu hooks

5 — Hooks: biến Claude thành teammateTrung cấp25 phút

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:

Bạn sẽ học được
  • 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 ở đâuLý do
Auto-format TS khi editproject/.claude/settings.jsonTeam convention
Block đọc .envproject/.claude/settings.jsonTeam security
Run typecheck sau editproject/.claude/settings.jsonTeam quality
Log mọi tool call của Claude vào file cá nhân để audit.claude/settings.local.jsonDebug cá nhân
Không cho Claude rm trong mọi project~/.claude/settings.jsonSafety 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 = ALLOW

6 ứ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 → stderr

3. 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 sensitive

4. 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.ts

5. Linting / convention enforcement

Claude auto-fix lint issue dựa trên feedback.

6. Logging / audit

Edit → PostToolUse: eslint, nếu warning → feedback

6. Logging / audit

Review sau đó: Claude đã chạy gì, khi nào.

Bash → PostToolUse: log mọi command vào audit.log

Ví 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.

Tài liệu tham khảo
  • 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
Nội dung này có hữu ích không?