Với project 50,000 dòng, Claude đôi khi:
- 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.
- Claude Code SDK docs
- Hooks production patterns — Anthropic blog
- Bài 4.14 — Basic hook implementation
- Bài 4.18 — SDK deep dive