Hooks hữu ích cho production — Type check + Duplicate prevention

5 — HooksNâng cao35 phút

Với project 50,000 dòng, Claude đôi khi:

Bạn sẽ học được
  • Build hook TypeScript typecheck feedback — bắt Claude quên update callsite
  • Build hook query duplication prevention — Claude không tạo duplicate DB query
  • Hiểu pattern "Claude review Claude" bằng Claude Code SDK
  • Đánh giá trade-off của expensive hook (latency, cost)
  • Áp dụng 2 hook này vào codebase thật của bạn

Hook 1: TypeScript typecheck feedback

Flow

Implement

File .claude/hooks/typecheck.js:

Config

settings.example.json:

┌──────────────────────────────────────────────────────────┐
│                                                          │
│  Claude Edit file.ts                                     │
│         │                                                │
│         ▼                                                │
│  [Hook chạy] tsc --noEmit                                │
│         │                                                │
│         ├── No error → exit 0, Claude continue           │
│         │                                                │
│         └── Error  → stderr có error list                │
│                      exit 1 (không block, feedback)      │
│                            │                             │
│                            ▼                             │
│  Claude thấy error → tự update call sites                │
│                                                          │
└──────────────────────────────────────────────────────────┘
#!/usr/bin/env node

const { execSync } = require('child_process');

async function readStdin() {
  const chunks = [];
  for await (const chunk of process.stdin) chunks.push(chunk);
  return Buffer.concat(chunks).toString();
}

async function main() {
  const input = JSON.parse(await readStdin());
  const path = input.tool_input?.file_path || '';
  
  // Skip non-TS files
  if (!/\.(ts|tsx)$/.test(path)) process.exit(0);
  
  // Skip test files — không block Claude vì test-only issue
  if (/\.test\.(ts|tsx)$/.test(path)) process.exit(0);
  
  try {
    // Chạy typecheck
    execSync('npx tsc --noEmit --pretty false', {
      stdio: 'pipe',
      timeout: 30_000,
    });
    // Pass — allow
    process.exit(0);
  } catch (err) {
    // Error output
    const stdout = err.stdout?.toString() || '';
    const stderr = err.stderr?.toString() || '';
    const errorOutput = stdout || stderr;
    
    // Feedback cho Claude qua stderr
    console.error(
      "TypeScript errors found after your edit:\n" +
      errorOutput +
      "\nPlease fix these errors before continuing."
    );
    
    // Exit 2 = push back để Claude fix
    process.exit(2);
  }
}

main().catch(err => {
  // Hook itself errored — don't block
  require('fs').appendFileSync('/tmp/claude-hook-errors.log', `${new Date()} typecheck hook: ${err}\n`);
  process.exit(1);
});

Config

Cách hoạt động với Claude

Claude edit lib/report.ts, add parameter. Hook chạy tsc. tsc báo 8 error ở 8 file khác.

Hook exit 2 với stderr:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node $PWD/.claude/hooks/typecheck.js"
          }
        ]
      }
    ]
  }
}

Cách hoạt động với Claude

Claude nhận stderr như feedback. Hiểu: "À, cần update call site." Tự động:

TypeScript errors found after your edit:
main.ts(15,5): error TS2554: Expected 2 arguments, but got 1.
dashboard.ts(42,7): error TS2554: Expected 2 arguments, but got 1.
...

Hook 1: TypeScript typecheck feedback (tiếp)

Sau khi fix hết → hook chạy lại → pass → Claude continue.

Kết quả: Zero type error sau session Claude Code. Không phải chờ CI catch.

Tương đương cho untyped languages

Cho Python: dùng mypy hoặc pyright. Cho Go: go build ./.... Cho Rust: cargo check. Cho JS (không TS): integration với eslint --no-fix + strict rules.

[Read] main.ts
[Edit] main.ts — thêm argument verbose=false
[Read] dashboard.ts
[Edit] dashboard.ts — thêm argument
...

Hook 2: Query duplication prevention

Vấn đề chi tiết

Project có structure:

Mỗi file có ~10-15 query function. Tổng ~40 function.

Claude được assign task "Slack integration". Task liên quan DB query nhỏ — Claude chỉ Grep file khá qua loa, không thấy helper getPendingOrders → viết lại.

Giải pháp: "Claude review Claude" pattern

PostToolUse hook khi Claude Write/Edit trong src/queries/:

Implement

File .claude/hooks/check-duplicate-query.js:

Key details:

Config

  • Hook launch Claude Code SDK (programmatic)
  • SDK Claude được task: "Review diff này vs các function có sẵn trong queries/. Có duplicate không? Nếu có, name function nào bị duplicate."
  • Output feedback cho main Claude
  • Main Claude fix (dùng existing function thay vì new)
  • Import @anthropic-ai/claude-code SDK (Bài 4.18 sẽ dạy sâu)
  • Sub-Claude có read-only permission default → chỉ đọc để review, không edit
  • maxTurns: 5 — giới hạn để không tốn quá nhiều
  • Feedback ra stderr, exit 2 — main Claude fix
src/
└── queries/
    ├── orders.ts       ← getPendingOrders, getShippedOrders, ...
    ├── users.ts        ← getActiveUsers, getUserByEmail, ...
    └── products.ts     ← getInStockProducts, getDiscountedProducts, ...
#!/usr/bin/env node

const { query } = require('@anthropic-ai/claude-code');

async function readStdin() {
  const chunks = [];
  for await (const chunk of process.stdin) chunks.push(chunk);
  return Buffer.concat(chunks).toString();
}

async function main() {
  const input = JSON.parse(await readStdin());
  const path = input.tool_input?.file_path || '';
  
  // Chỉ chạy cho queries/
  if (!path.includes('/queries/')) process.exit(0);
  
  // Skip test
  if (path.includes('.test.')) process.exit(0);
  
  // Extract modified content
  const newContent = input.tool_input?.new_string || input.tool_input?.content || '';
  
  // Launch sub-Claude để review
  const reviewPrompt = `
    Look at the queries in the ./src/queries directory.
    
    A new or modified query has just been added. Here is the change:
    
    File: ${path}
    ${newContent ? 'Content:\n' + newContent : ''}
    
    Check:
    1. Is this query duplicating functionality of an existing query 
       in the queries/ directory?
    2. If yes, name the existing query and explain the overlap.
    3. If no, answer "NO DUPLICATE".
    
    Be terse. Max 5 lines.
  `;
  
  let feedback = '';
  try {
    for await (const message of query({
      prompt: reviewPrompt,
      options: { 
        maxTurns: 5,
      },
    })) {
      if (message.type === 'text') feedback += message.text;
    }
  } catch (err) {
    require('fs').appendFileSync('/tmp/claude-dup-hook.log', `${err}\n`);
    process.exit(1); // hook errored — don't block
  }
  
  if (feedback.includes("NO DUPLICATE")) {
    process.exit(0);
  }
  
  // Feedback về main Claude
  console.error(
    "Possible duplicate query detected:\n\n" +
    feedback +
    "\n\nConsider using the existing function instead of creating a new one."
  );
  process.exit(2);
}

main().catch(err => {
  require('fs').appendFileSync('/tmp/claude-dup-hook.log', `FATAL: ${err}\n`);
  process.exit(1);
});

Config

Trade-offs — Quan trọng đọc

Chi phí

Hook này launch sub-Claude session mỗi khi write/edit file trong queries/. Mỗi launch:

Math: Nếu session Claude Code bạn edit 10 file trong queries/ → 10 sub-Claude session = 20,000-50,000 token phụ.

Khi đáng:

Khi không đáng:

Mở rộng pattern

Pattern "Claude review Claude" áp dụng được cho nhiều domain:

Hook tập trung domain cụ thể → ít noise, hiệu quả.

Khuyến nghị scope

Chỉ bật hook expensive (như duplicate check) cho:

Không bật global. Ratio benefit/cost phải được tính toán.

  • ~5-10 giây latency
  • ~2,000-5,000 token usage
  • Project lớn, duplication là vấn đề lặp
  • Direction mới nào cũng có 20-40 query sẵn
  • Project nhỏ, dev review thủ công cũng thấy
  • Cost AI là concern (startup scrappy)
  • Code quality review — check code smell, anti-pattern
  • Security review — check security issue (SQL injection, XSS)
  • Performance review — check N+1 query, inefficient loop
  • Documentation review — check function có docstring, đúng format
  • Specific folder (critical directory)
  • Specific file type
  • Specific Claude Code user (senior dev, CI)
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node $PWD/.claude/hooks/check-duplicate-query.js"
          }
        ]
      }
    ]
  }
}

Ví dụ thực chiến: Fintech team rollout 2 hook

Setup

Team 15 dev, fintech. Setup:

Tuần 1 — Pilot senior engineer

1 senior dev bật cả 2 hook. Quan sát 1 tuần:

Tuần 2 — Rollout team

Merge vào .claude/settings.example.json. Dev khác pull + npm run setup.

Tháng 1 — Metrics

Tối ưu

Dev feedback "đôi khi lag" → điều chỉnh:

Latency giảm 40%, kết quả vẫn tốt.

  • Typecheck hook — mọi edit TS
  • Duplicate query hook — chỉ trong src/queries/ (critical)
  • Typecheck hook bắt 8 type error Claude sẽ leave behind
  • Duplicate hook bắt 3 trường hợp dup
  • Extra latency per task: 5-15s (acceptable)
  • PR with TypeScript error sau merge: từ 8/tháng xuống 0
  • Duplicate query phát hiện trước merge: 12 cases
  • AI cost tăng: $200/tháng (cho toàn team 15 người) — đáng so với dev time saved
  • Dev satisfaction: 90% "muốn giữ", 10% "lag đôi khi"
  • Typecheck: chạy tsc incremental (tsc -b) thay full
  • Duplicate hook: chỉ chạy cho file > 50 LOC (file nhỏ thường không dup)

Case studies hook production

💰 Fintech — Compliance check hook

Setup: PostToolUse hook sau edit file trong /billing/ → chạy sub-Claude check: "Does this change any amount calculation? If so, are units (cents/dollars) correct?"

🏥 Health tech — PHI leak prevention

Setup: PostToolUse hook sau Write → sub-Claude scan for hardcoded PHI patterns (fake SSN, fake email) in fixtures.

🛠️ Platform eng — API consistency

Setup: PostToolUse sau edit route file → sub-Claude check: "Does this new endpoint follow the REST pattern in @docs/api-conventions.md?"

🎮 Game dev — Balance data validation

Setup: PostToolUse sau edit file balance/*.json → sub-Claude check: "Does this change break relative power balance (e.g., tier-3 unit weaker than tier-2)?"

📣 Marketing — Brand voice check

Setup: PostToolUse sau edit content/*.md → sub-Claude check: "Does this match @brand/voice-guide.md tone?"

  • Kết quả: Catch được 5 bug math (sai units) trong 2 tháng — saved potential $$$ refund.
  • Kết quả: 100% test fixtures compliant, audit pass.
  • Kết quả: API consistency score lên 95% (từ 70%), đỡ 30% churn frontend dev.
  • Kết quả: Zero broken balance in release builds.
  • Kết quả: Copy consistency, 50% less editing rounds.

Anti-patterns hook production

❌ Sub-Claude permission leak

Biểu hiện: Sub-Claude trong hook có allowedTools: ["Edit"] → có thể modify file.

Rủi ro: Sub-Claude gây race condition với main Claude; unexpected edit.

Cách đúng: Sub-Claude read-only default. Chỉ grant write khi thực sự cần.

❌ Hook recursive

Biểu hiện: Hook trigger tool call → tool call trigger hook → infinite loop.

Hậu quả: Claude Code hang, tốn token.

Cách đúng: Sub-Claude (trong hook) phải skip hook (qua env var CLAUDE_HOOKS_DISABLE=1 hoặc setup riêng).

❌ Feedback hook quá mơ hồ

Biểu hiện: Hook output "something wrong with your code" — Claude không hiểu fix gì.

Cách đúng: Feedback specific: file name, line, reason, suggested fix.

❌ Hook chạy trên file khổng lồ

Biểu hiện: Hook load file 10MB vào sub-Claude → cost + time explode.

Cách đúng: Skip large files, hoặc chỉ gửi diff thay vì toàn bộ file.

❌ Không log metrics

Biểu hiện: Chạy hook 3 tháng, không biết catch rate, cost, latency.

Cách đúng: Log mỗi trigger vào file với: timestamp, duration, catch (yes/no). Dashboard đơn giản.

Mẹo nâng cao

Mẹo 1: Hook "expensive" opt-in

Thay vì luôn bật, set flag:

Hook đọc env var, enable/skip accordingly.

Mẹo 2: Cache sub-Claude response

Nếu cùng file không đổi → reuse result từ lần trước (dùng file hash key).

Mẹo 3: Parallel hook chain

Nếu có 3 hook independent → chạy parallel thay vì sequential. Dùng Promise.all trong wrapper script.

Mẹo 4: Hook dev vs prod

  • Dev branch: strict hooks (typecheck, dup check, security)
  • Prod branch: chỉ formatting (fast, không lag release)
# Cần review chặt
export CLAUDE_HOOKS_LEVEL=strict
claude

# Chỉ quick edit
export CLAUDE_HOOKS_LEVEL=light
claude

Áp dụng ngay

Bài tập 1: Typecheck hook (20 phút)

Bước 1: Copy script typecheck trên vào .claude/hooks/typecheck.js.

Bước 2: Config trong settings.local.json.

Bước 3: Test:

Ghi:

Bài tập 2 (optional, 30 phút): Build custom review hook

Chọn concern của project bạn (security/performance/consistency/docs). Viết hook PostToolUse dùng SDK sub-Claude để review.

Template:

Test với 1-2 scenario. Ghi:

  • Edit file TS một hàm, intentional add parameter
  • Không update call site
  • Claude phản ứng gì?
  • Hook có bắt được error? ☐ Có ☐ Không
  • Claude có tự fix? ☐ Có ☐ Không
  • Latency thêm bao nhiêu (giây)? ____
  • Catch đúng: ____________
  • False positive: ____________
  • Avg latency: ____________
const reviewPrompt = `
  Review the change to ${path}. Check: [your specific concern].
  Answer "PASS" or "FAIL: <reason>".
`;

Tóm tắt bài học

🎯 Typecheck hook = compile safety net — Claude không để type error leak vào CI.

🎯 Duplicate hook = "Claude review Claude" — dùng SDK để review output Claude.

🎯 Expensive hook phải scope hẹp — specific folder, file type, branch.

🎯 Feedback qua stderr + exit 2 — main Claude tự fix.

🎯 Pattern mở rộng đa domain — security, performance, consistency, docs review.

Tài liệu tham khảo
  • Claude Code SDK docs
  • Hooks production patterns — Anthropic blog
  • Bài 4.14 — Basic hook implementation
  • Bài 4.18 — SDK deep dive
Nội dung này có hữu ích không?