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
Mở đầu: Khi codebase lớn, Claude có "trí nhớ kém"
Vấn đề 1: Sửa function, quên call site
Task: "Thêm parameter verbose: boolean vào formatReport() trong lib/report.ts"
Claude:
[Edit] lib/report.ts — thêm parameter, sửa signature
Xong, exit.
Nhưng formatReport() được gọi ở 8 file khác. Claude không update những call site đó. Type error lan ra khắp project — bạn phát hiện sau khi chạy tsc tay.
Vấn đề 2: Tạo duplicate query
Task: "Tạo Slack integration báo order pending > 3 ngày."
Claude:
[Write] src/integrations/slack-orders.ts
- Viết new query: SELECT * FROM orders WHERE status = 'pending' AND created_at < NOW() - INTERVAL '3 days'
Nhưng trong src/queries/orders.ts đã có sẵn getPendingOrders(olderThanDays: number) — Claude không biết hoặc không care. Giờ codebase có 2 function làm cùng việc.
Giải pháp: Hook thông minh
Hai vấn đề trên giải bằng 2 hook mạnh:
- Typecheck feedback loopPostToolUse chạy
tsc, feedback error về Claude → Claude tự fix - Duplicate preventionPostToolUse gọi Claude Code SDK review code vừa write, detect duplicate với existing, feedback
Bài này build cả hai.
Hook 1: TypeScript typecheck feedback
Flow
┌──────────────────────────────────────────────────────────┐
│ │
│ 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 │
│ │
└──────────────────────────────────────────────────────────┘
Implement
File .claude/hooks/typecheck.js:
#!/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
settings.example.json:
{
"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 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:
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.
...
Claude nhận stderr như feedback. Hiểu: "À, cần update call site." Tự động:
[Read] main.ts
[Edit] main.ts — thêm argument verbose=false
[Read] dashboard.ts
[Edit] dashboard.ts — thêm argument
...
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.
Hook 2: Query duplication prevention
Vấn đề chi tiết
Project có structure:
src/
└── queries/
├── orders.ts ← getPendingOrders, getShippedOrders, ...
├── users.ts ← getActiveUsers, getUserByEmail, ...
└── products.ts ← getInStockProducts, getDiscountedProducts, ...
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/:
- 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)
Implement
File .claude/hooks/check-duplicate-query.js:
#!/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);
});
Key details:
- Import
@anthropic-ai/claude-codeSDK (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
Config
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node $PWD/.claude/hooks/check-duplicate-query.js"
}
]
}
]
}
}
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:
- ~5-10 giây latency
- ~2,000-5,000 token usage
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:
- Project lớn, duplication là vấn đề lặp
- Direction mới nào cũng có 20-40 query sẵn
Khi không đáng:
- Project nhỏ, dev review thủ công cũng thấy
- Cost AI là concern (startup scrappy)
Mở rộng pattern
Pattern "Claude review Claude" áp dụng được cho nhiều domain:
- 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
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:
- Specific folder (critical directory)
- Specific file type
- Specific Claude Code user (senior dev, CI)
Không bật global. Ratio benefit/cost phải được tính toán.
Ví dụ thực chiến: Fintech team rollout 2 hook
Setup
Team 15 dev, fintech. Setup:
- Typecheck hookmọi edit TS
- Duplicate query hookchỉ trong
src/queries/(critical)
Tuần 1 — Pilot senior engineer
1 senior dev bật cả 2 hook. Quan sát 1 tuần:
- 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)
Tuần 2 — Rollout team
Merge vào .claude/settings.example.json. Dev khác pull + npm run setup.
Tháng 1 — Metrics
- 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"
Tối ưu
Dev feedback "đôi khi lag" → điều chỉnh:
- 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)
Latency giảm 40%, kết quả vẫn tốt.
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?"
- Kết quả: Catch được 5 bug math (sai units) trong 2 tháng — saved potential $$$ refund.
Health tech — PHI leak prevention
Setup: PostToolUse hook sau Write → sub-Claude scan for hardcoded PHI patterns (fake SSN, fake email) in fixtures.
- Kết quả: 100% test fixtures compliant, audit pass.
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?"
- Kết quả: API consistency score lên 95% (từ 70%), đỡ 30% churn frontend dev.
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)?"
- Kết quả: Zero broken balance in release builds.
Marketing — Brand voice check
Setup: PostToolUse sau edit content/*.md → sub-Claude check: "Does this match @brand/voice-guide.md tone?"
- 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=1hoặ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:
export CLAUDE_HOOKS_LEVEL=strict
claude
export CLAUDE_HOOKS_LEVEL=light
claude
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)
Á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:
- Edit file TS một hàm, intentional add parameter
- Không update call site
- Claude phản ứng gì?
Ghi:
- Hook có bắt được error? Có Không
- Claude có tự fix? Có Không
- Latency thêm bao nhiêu (giây)? ____
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:
const reviewPrompt = `
Review the change to ${path}. Check: [your specific concern].
Answer "PASS" or "FAIL: <reason>".
`;
Test với 1-2 scenario. Ghi:
- Catch đúng: ____________
- False positive: ____________
- Avg latency: ____________
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