← Lập trình với Claude Code

Bài 9 · Nâng cao · 22 phút· Cập nhật 11/06/2026

Hooks: tự động hoá vòng đời

Biên soạn bởi Nguyễn Anh Tuấn

Chạy lệnh tại các mốc (PreToolUse/PostToolUse/Stop…): tự format/test, chặn lệnh nguy hiểm; cấu hình trong settings.json, mã thoát.

CLAUDE.md rất hữu ích, nhưng nó là lời khuyên - Claude cố tuân theo, đôi khi vẫn quên. Khi bạn cần một điều luôn luôn xảy ra (format sau mỗi lần sửa, chặn lệnh xoá nguy hiểm), bạn cần đảm bảo, không phải lời khuyên. Đó là hooks: mã do bạn cấu hình, Claude Code tự chạy tại các mốc vòng đời cố định.

  • CLAUDE.md = LỜI KHUYÊN (có thể quên); Hook = ĐẢM BẢO (luôn chạy, do máy thực thi).
  • Hook chạy tại các mốc: trước/sau khi dùng công cụ, đầu phiên, khi Claude trả lời xong…
  • Dùng để: tự format/test, chặn lệnh nguy hiểm, ghi log, nhắc nhở.

Hook gắn vào một “mốc”. Vài mốc hay dùng nhất:

MốcKhi nàoChặn được?
SessionStartMở/khôi phục phiênkhông
UserPromptSubmitBạn gửi một prompt
PreToolUseTRƯỚC khi chạy một công cụ
PostToolUseSAU khi công cụ chạy xongđưa ý cho Claude
StopClaude trả lời xongcó (bắt làm tiếp)
PreCompactTrước khi nén ngữ cảnh
SessionEndKết thúc phiênkhông
  • PreToolUse là mốc mạnh nhất: chạy TRƯỚC công cụ → CHẶN được hành động.
  • PostToolUse chạy SAU → hợp để tự format/test (công cụ đã chạy, không chặn nữa).
  • Stop chặn được để buộc Claude làm tiếp (vd “chưa chạy test thì chưa dừng”).

Thử ngay - bật/tắt hook để xem vòng đời thay đổi ra sao:

Ba lệnh gọi cố định: Edit(src/app.ts), Bash(rm -rf build), Bash(npm test). Bật/tắt hook để xem thứ tự vòng đời thay đổi ra sao:

Timeline vòng đời

  • ⏺ công cụ Edit(src/app.ts)
  • PostToolUse PostToolUse: tự format (Edit|Write) src/app.ts
  • PreToolUse PreToolUse: chặn rm (Bash) "rm " khớp trong "rm -rf build"
  • ⏺ công cụ Bash(rm -rf build) bị chặn bởi hook
  • PreToolUse PreToolUse: chặn rm (Bash) npm test
  • ⏺ công cụ Bash(npm test)

Bị chặn (1): rm -rf build

Hook khai báo trong settings.json theo cấu trúc: Mốc → matcher (lọc công cụ) → danh sách hook. Ví dụ kinh điển: tự chạy linter sau mỗi lần Claude ghi/sửa file:

.claude/settings.json - tự lint sau khi sửa file

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npm run lint:fix"
          }
        ]
      }
    ]
  }
}
  • matcher lọc công cụ: "Bash", "Edit|Write" (nhiều cái), "*" (mọi công cụ).
  • Mỗi hook có type - phổ biến nhất là "command" (chạy một script shell).
  • Nơi đặt: .claude/settings.json (dự án) · ~/.claude/settings.json (cá nhân) · .claude/settings.local.json (cục bộ).

Mốc PreToolUse chạy TRƯỚC công cụ, nên nó chặn được. Hook nhận dữ liệu sự kiện qua stdin (JSON) và quyết định bằng mã thoát hoặc JSON trả về:

.claude/hooks/block-rm.sh - chặn rm -rf

#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Chan lenh rm -rf theo chinh sach"
    }
  }'
else
  exit 0
fi
  • Hook nhận JSON qua stdin: session_id, tool_name, tool_input… (dùng jq để đọc).
  • Mã thoát 0 = ổn; 2 = CHẶN (stderr đưa cho Claude); mã khác = không chặn, ghi log.
  • Cách hiện đại: in JSON với permissionDecision "deny" + lý do - rõ ràng cho người dùng.
  • Trỏ tới script bằng ${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh để chắc đường dẫn.

Ngoài command (chạy script), hook còn có thể là: http (gọi một dịch vụ), mcp_tool (gọi công cụ MCP), prompt (hỏi một model đánh giá), hay agent (một subagent kiểm tra). Phần lớn việc thường ngày chỉ cần command.

Sức mạnh đi kèm rủi ro

Hook chạy mã của bạn với quyền của bạn, tự động - một hook sai có thể xoá nhầm, làm rò rỉ bí mật, hoặc làm chậm mọi thao tác. Chỉ bật hook bạn hiểu rõ; với hook đến từ repo hay plugin của người khác, đọc kỹ trước khi bật. Gõ /hooks để xem hook nào đang chạy.

Tiếp theo: MCP

Hook tự động hoá quanh Claude Code. Bài kế tiếp - MCP - mở rộng những gì Claude với tới được: kết nối tới công cụ & dữ liệu ngoài (GitHub, cơ sở dữ liệu, dịch vụ…).

Câu hỏi thường gặp

CLAUDE.md là LỜI KHUYÊN: Claude đọc và CỐ tuân theo, nhưng vẫn có thể quên/bỏ qua. Hook là ĐẢM BẢO: một đoạn mã do bạn cấu hình, Claude Code TỰ chạy tại một mốc cố định - không phụ thuộc vào việc Claude “nhớ” hay không. Cần “mỗi lần sửa file phải format” thì hook chắc chắn hơn nhắc trong CLAUDE.md.

Nhiều, nhưng hay dùng nhất: SessionStart (mở phiên), UserPromptSubmit (bạn gửi prompt), PreToolUse (TRƯỚC khi chạy công cụ - chặn được), PostToolUse (SAU khi công cụ chạy xong), Stop (Claude trả lời xong - chặn để bắt làm tiếp), SubagentStop, PreCompact (trước khi nén ngữ cảnh), SessionEnd. Một số mốc CHẶN được hành động, một số chỉ để quan sát.

Trong settings.json (mục hooks): cấp dự án .claude/settings.json (chia sẻ qua git), cấp cá nhân ~/.claude/settings.json, hoặc cục bộ .claude/settings.local.json. Plugin cũng đóng gói hook được (hooks/hooks.json). Gõ /hooks để xem các hook đang bật.

0 = thành công (nếu in JSON ra stdout sẽ được xử lý). 2 = lỗi CHẶN: stderr được đưa cho Claude/người dùng và hành động bị chặn (vd PreToolUse → không cho chạy công cụ; Stop → bắt Claude làm tiếp). Mã khác = lỗi KHÔNG chặn: ghi vào log, vẫn chạy tiếp. Hook nhận dữ liệu sự kiện dưới dạng JSON qua stdin.

Hai cái: (1) PostToolUse trên Write|Edit → tự chạy formatter/linter trên file vừa sửa (code luôn sạch, khỏi nhắc). (2) PreToolUse trên Bash → chặn lệnh nguy hiểm như rm -rf. Cả hai biến quy ước “mong manh” thành luật cứng được máy thực thi.

Hook chạy MÃ CỦA BẠN với QUYỀN của bạn, tự động. Đó là sức mạnh nhưng cũng là rủi ro: một hook sai có thể xoá nhầm, rò rỉ bí mật, hay làm chậm mọi thao tác. Chỉ cài hook bạn hiểu rõ; cẩn trọng với hook đến từ repo/plugin của người khác (đọc trước khi bật).

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Khác biệt cốt lõi giữa CLAUDE.md và hook là gì?

  1. 1

    Khuyên vs đảm bảo

    Bằng lời: vì sao “tự chạy prettier sau mỗi lần sửa” nên là HOOK chứ không phải một dòng trong CLAUDE.md?

    Hoàn thành khi: CLAUDE.md chỉ là lời khuyên Claude có thể quên; hook PostToolUse chạy chắc chắn mỗi lần → kết quả nhất quán.

  2. 2

    Hook tự format

    Thêm hook PostToolUse cho Write|Edit chạy formatter của dự án bạn (vd prettier/black) trên file vừa sửa. Sửa thử một file.

    Hoàn thành khi: Sau mỗi lần Claude sửa file, formatter tự chạy; bạn không phải nhắc “format lại giúp”.

  3. 3

    Chặn lệnh nguy hiểm

    Viết một hook PreToolUse trên Bash dùng if "Bash(rm *)" để chặn (hoặc cảnh báo) lệnh xoá. Thử nhờ Claude chạy rm.

    Hoàn thành khi: Lệnh rm bị chặn (mã thoát 2 hoặc permissionDecision: deny); bạn thấy lý do hiện ra.

  4. 4

    Đọc dữ liệu sự kiện

    Viết một script hook in ra (vd ghi log) trường tool_nametool_input nhận từ stdin để xem hook “thấy” gì.

    Hoàn thành khi: Bạn quan sát được JSON sự kiện (session_id, tool_name, tool_input…) mà Claude Code truyền cho hook.

  5. 5

    Xem hook đang bật

    /hooks để liệt kê hook đang hoạt động và nguồn của chúng (dự án/cá nhân/plugin).

    Hoàn thành khi: Bạn thấy danh sách hook + nguồn; biết hook nào áp cho phiên hiện tại.