- Published on
Day 12 - Hooks trong Claude Code
- Authors

- Name
- Trần Mạnh Thắng
- @TranManhThang96
1. Mục tiêu bài học
Sau khoảng 2 tiếng, học viên có thể:
- Giải thích được hook trong Claude Code là gì, chạy ở điểm nào trong agentic workflow và vì sao hook khác với prompt, permission và slash command.
- Cấu hình hook ở đúng scope: user, project hoặc local; biết khi nào nên commit
.claude/settings.jsonvà khi nào chỉ dùng.claude/settings.local.json. - Dùng
PreToolUse,PostToolUse,UserPromptSubmitvàSessionStartcho đúng mục đích. - Tạo hook chặn command nguy hiểm như
rm -rftrước khi Bash tool chạy. - Tạo hook chạy formatter sau khi Claude Code sửa file, nhưng chỉ trong matcher hẹp và extension được kiểm soát.
- Tạo hook ghi log command phục vụ audit/debug mà không làm lộ secret.
- Debug hook bằng
/hooks,claude --debug,claude --debug-file, log file riêng và cách disable hook khi workflow bị kẹt. - Nhận diện rủi ro: hook chạy command tự động, timeout, JSON escaping sai, matcher quá rộng, log chứa secret, hook gây side effect khó truy vết.
2. Bối cảnh thực tế
Khi team dùng Claude Code trên repo thật như taskflow-ai, vấn đề không chỉ là "Claude có viết code đúng không". Vấn đề lớn hơn là Claude Code có thể chạy tool: đọc file, sửa file, chạy Bash command, tạo test, format code, gọi script. Các hành động đó xảy ra nhanh, nhiều khi nằm giữa một phiên làm việc dài. Nếu mọi thứ chỉ dựa vào human review sau cùng, một command nguy hiểm hoặc một edit ngoài scope có thể đã xảy ra trước khi bạn nhìn thấy diff.
Hook trong Claude Code là cơ chế event-driven automation: khi một sự kiện xảy ra trong session, Claude Code gửi JSON input vào một script hoặc command bạn cấu hình. Script đó có thể kiểm tra, ghi log, trả feedback hoặc block hành động tùy event. Hook giúp biến rule an toàn của team thành guardrail tự động.
Ví dụ trong taskflow-ai:
- Trước khi Bash chạy, chặn
rm -rf,docker compose down -v, migration destructive hoặc command đụng production database. - Sau khi Claude Code dùng
EdithoặcWrite, chạy formatter cho đúng file vừa sửa. - Sau mỗi Bash command, ghi audit log gồm thời gian, session id, working directory và command đã redacted.
- Khi user submit prompt, block prompt chứa secret hoặc production credential.
- Khi session bắt đầu, nhắc Claude Code đọc
CLAUDE.md, hiển thị project context hoặc ghi session start log.
Không nên dùng hook khi:
- Bạn chưa hiểu rõ command hook sẽ chạy với quyền gì trên máy mình.
- Logic hook phụ thuộc network, service ngoài, database hoặc state không ổn định.
- Hook làm thay đổi code ngoài file mà Claude vừa sửa.
- Hook chạy command destructive như
rm,git reset,git clean,docker compose down -v, migration rollback hoặc xóa cache rộng. - Team chưa có cách debug/disable hook khi nó block nhầm.
Hook là guardrail, không phải thay thế cho review. Nếu hook quá thông minh, quá rộng hoặc tự sửa nhiều thứ, nó có thể biến workflow thành một hệ thống khó debug hơn cả việc để Claude Code chạy tự do.
3. Kiến thức nền
Hook là gì
Hook là command hoặc script được Claude Code tự động chạy khi có event. Với command hook, Claude Code truyền dữ liệu event qua stdin dưới dạng JSON. Script đọc JSON này, quyết định làm gì, rồi trả kết quả qua exit code, stdout hoặc stderr.
Luồng tối giản:
Claude Code event
|
v
Matcher trong settings có khớp không?
|
+-- Không -> bỏ qua hook
|
v
Chạy hook command, truyền JSON vào stdin
|
v
Hook đọc tool_name, tool_input, cwd, session_id...
|
v
Exit 0 -> tiếp tục / ghi output tùy event
Exit 2 -> block ở event hỗ trợ block, stderr gửi lại Claude
Exit khác -> lỗi hook, thường không block nhưng được log
Hook thường nằm trong settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path trong ví dụ: taskflow-ai/.claude/settings.local.json.
Mục đích: cấu hình một hook chạy trước Bash tool, chỉ khi command trông giống rm ..., rồi gọi script trong project.
Cách test: mở Claude Code tại root taskflow-ai, yêu cầu chạy một command an toàn như pwd, sau đó thử prompt yêu cầu rm -rf tmp. Hook phải không ảnh hưởng pwd và phải block command nguy hiểm.
Edge cases: khi tham chiếu script trong project, ưu tiên exec form với ${CLAUDE_PROJECT_DIR} và args: [] để tránh lỗi shell quoting khi path có khoảng trắng. Trường if chỉ là filter phụ; script vẫn phải tự kiểm tra command vì matcher có thể quá rộng hoặc Claude Code version khác có behavior khác.
Scope cấu hình
Claude Code có nhiều nơi đặt settings. Chọn sai scope là lỗi phổ biến trong team.
| Scope | File thường dùng | Khi dùng | Rủi ro |
|---|---|---|---|
| User | ~/.claude/settings.json | Rule cá nhân áp dụng mọi repo, ví dụ log command local | Dễ làm repo khác bị ảnh hưởng; không nên chứa rule project-specific |
| Project shared | taskflow-ai/.claude/settings.json | Guardrail team muốn commit vào repo, ví dụ chặn command destructive | Hook chạy cho mọi người; phải review kỹ, cross-platform nếu team dùng nhiều OS |
| Project local | taskflow-ai/.claude/settings.local.json | Lab, thử nghiệm, secret/local path, rule cá nhân trong project | Không share cho team; người khác không có guardrail này |
| Managed/enterprise | Managed settings | Policy tổ chức | Developer local khó override; cần quy trình thay đổi rõ |
Trong Day 12, thực hành mặc định dùng .claude/settings.local.json để không ảnh hưởng worker khác. Chỉ promote sang .claude/settings.json khi team đã review script, matcher, timeout và logging policy.
Event quan trọng trong bài này
| Event | Chạy khi nào | Use case tốt | Không nên dùng để |
|---|---|---|---|
PreToolUse | Trước khi một tool chạy | Block command nguy hiểm, bảo vệ file nhạy cảm, yêu cầu confirmation | Chạy formatter hoặc sửa code vì tool chưa chạy |
PostToolUse | Sau khi tool chạy | Format file vừa sửa, log activity, phân tích output | Chặn hành động đã xảy ra; nếu cần block thì dùng PreToolUse |
UserPromptSubmit | Khi user vừa submit prompt, trước khi Claude xử lý | Block prompt chứa secret, thêm context ngắn, log prompt metadata | Log toàn bộ prompt nếu prompt có thể chứa credential |
SessionStart | Khi session start/resume/clear/compact tùy matcher | Nạp context an toàn, ghi session start, nhắc rule | Chạy setup nặng, install dependency, reset state |
Các event khác như Stop, SubagentStop, PreCompact, Notification, SessionEnd cũng hữu ích, nhưng Day 12 tập trung vào 4 event đủ dùng cho workflow team.
Ví dụ UserPromptSubmit để chặn prompt có dấu hiệu chứa secret:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-prompt-secret.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: chạy script trước khi Claude xử lý prompt của user. Với UserPromptSubmit, nếu cần block prompt, command hook nên trả JSON có "decision": "block" và "reason" thay vì chỉ dựa vào stderr.
Cách test: dùng prompt chứa token giả như token=fake-value-for-redaction-test, không dùng secret thật. Hook phải block prompt và không đưa prompt đó vào context.
Edge cases: không log raw prompt. Prompt có thể chứa credential thật do user paste nhầm; nếu hook ghi prompt vào log thì hook trở thành nguồn leak mới.
Script tối giản cho ví dụ trên:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
PROMPT="$(printf '%s' "$INPUT" | jq -r '.prompt // ""')"
if printf '%s' "$PROMPT" | grep -Eiq '(BEGIN (RSA|OPENSSH|EC) PRIVATE KEY|api[_-]?key=|token=|password=|authorization:)'; then
jq -nc --arg reason "Prompt appears to contain a secret. Remove credentials before asking Claude Code to process it." \
'{decision:"block", reason:$reason}'
exit 0
fi
exit 0
File path: taskflow-ai/.claude/hooks/check-prompt-secret.sh.
Mục đích: kiểm tra prompt user submit, block bằng JSON decision nếu thấy pattern nhạy cảm.
Cách test: cấu hình hook local, mở Claude Code, gửi prompt có token giả. Output kỳ vọng là prompt bị block với reason rõ ràng.
Edge cases: regex chỉ là lớp bảo vệ thô, có false positive và false negative. Không dùng hook này để chứng minh prompt "sạch" tuyệt đối.
Ví dụ SessionStart để thêm context ngắn khi mở hoặc resume session:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-context.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: chạy hook khi session mới bắt đầu hoặc resume. SessionStart chỉ nên tạo context nhẹ, ví dụ branch hiện tại và nhắc không log secret.
Cách test: mở Claude Code từ root taskflow-ai, xem transcript/context đầu session hoặc debug log để xác nhận hook chạy.
Edge cases: SessionStart chạy lại khi resume, vì vậy không đặt setup nặng hoặc command có side effect ở đây.
Script tối giản:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
SOURCE="$(printf '%s' "$INPUT" | jq -r '.source // "unknown"')"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
cd "$PROJECT_DIR"
BRANCH="$(git branch --show-current 2>/dev/null || true)"
jq -nc \
--arg source "$SOURCE" \
--arg branch "$BRANCH" \
'{hookSpecificOutput:{hookEventName:"SessionStart", additionalContext:("Session source: " + $source + "\nCurrent branch: " + $branch + "\nReminder: do not read or log .env, secrets, tokens, or production data.")}}'
File path: taskflow-ai/.claude/hooks/session-context.sh.
Mục đích: thêm context ngắn đầu session mà không đọc file nhạy cảm.
Cách test: chạy Claude Code với debug file và kiểm tra SessionStart hook completed status 0.
Edge cases: stdout của SessionStart có thể đi vào context, nên không in output dài, không in git diff, không in environment variable.
JSON input của hook
Với PreToolUse hoặc PostToolUse cho Bash, hook nhận input tương tự:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/taskflow-ai",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
File path: input này không phải file bạn tạo; nó là payload Claude Code truyền vào script qua stdin.
Mục đích: script dùng tool_name, tool_input.command, tool_input.file_path, cwd, session_id để quyết định.
Cách test: trong script hook, tạm log payload đã redacted vào .claude/logs/hook-input.sample.jsonl, rồi xóa log sau khi hiểu schema. Không log payload thật lâu dài nếu nó có thể chứa secret.
Edge cases: mỗi tool có tool_input khác nhau. Bash có command; Edit và Write có file_path; MultiEdit cũng có file_path nhưng khác shape về edits. Script phải dùng fallback như .tool_input.file_path // empty thay vì giả định field luôn tồn tại.
Matcher và if
matcher lọc hook theo tool hoặc source. Với PreToolUse và PostToolUse, matcher thường là tên tool:
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh",
"args": [],
"timeout": 20
}
]
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: hook chạy sau các tool edit/write.
Cách test: yêu cầu Claude Code sửa một file .ts nhỏ, rồi kiểm tra formatter log.
Edge cases: matcher quá rộng như "*" làm hook chạy trên mọi tool, dễ chậm và khó debug. Nếu dùng if, hãy coi nó là lớp lọc phụ, không phải lớp bảo mật duy nhất.
Claude Code hỗ trợ filter if trong hook để thu hẹp theo cú pháp permission rule, ví dụ:
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
Rủi ro: nếu Claude Code version cũ không hỗ trợ if hoặc cú pháp không khớp như bạn nghĩ, hook có thể chạy nhiều hơn dự kiến. Vì vậy script vẫn phải tự validate input.
Exit code và output
Với command hook:
| Exit code | Ý nghĩa thực tế | Dùng khi |
|---|---|---|
0 | Hook chạy thành công; Claude Code tiếp tục. Tùy event, stdout có thể được hiển thị hoặc đưa vào context. | Log thành công, format thành công, không có vi phạm |
2 | Blocking error ở event hỗ trợ block; stderr được đưa lại cho Claude làm feedback. | Chặn rm -rf, chặn prompt chứa secret, chặn sửa file protected |
Khác 0/2 | Lỗi hook không block trong phần lớn event, nhưng được log/debug. | Bug trong hook; cần sửa script |
Với PreToolUse, exit 2 là cách đơn giản để chặn tool call. Nếu cần kiểm soát tinh hơn như allow/deny/ask/defer hoặc sửa tool_input, dùng JSON hookSpecificOutput.permissionDecision và exit 0; không trộn JSON control với exit 2 vì Claude Code chỉ parse JSON khi hook thành công. Với PostToolUse, hành động tool đã xảy ra, nên exit 2 chỉ gửi stderr làm feedback cho Claude, không undo được file edit hoặc command đã chạy. Nếu cần ngăn việc xảy ra, đặt rule ở PreToolUse.
Timeout
Command hook, HTTP hook và MCP tool hook thường có timeout mặc định 600 giây; riêng UserPromptSubmit hạ default của các loại này xuống 30 giây. Prompt hook mặc định 30 giây, agent hook mặc định 60 giây. Các default này đủ để tránh treo vô hạn, nhưng trong project thật không nên dựa vào default vì session sẽ bị chậm hoặc khó hiểu khi hook kẹt. Hãy đặt timeout ngắn theo mục đích:
- Block command:
3-5giây. - Log activity:
3-5giây. - Formatter sau edit:
15-30giây cho file đơn lẻ, không format cả repo. - SessionStart context:
5-10giây.
Hook càng lâu, Claude Code session càng chậm vì hook đồng bộ sẽ block Claude cho tới khi hoàn tất. Claude Code có hỗ trợ async cho command hook, nhưng async hook không thể block hoặc điều khiển quyết định; dùng async cho job quan sát dài, không dùng cho safety gate. Hook formatter chạy npm run format trên toàn repo sau mỗi edit là ví dụ tệ: nó vừa tốn thời gian, vừa có thể sửa rất nhiều file ngoài scope, vừa làm diff khó review.
4. Step-by-step thực hành
Mục tiêu thực hành: trong project taskflow-ai, tạo 3 hook local:
PreToolUsechặnrm -rf.PostToolUsechạy formatter sau khi Claude Code sửa file.PostToolUseghi log Bash command đã redacted.
Tất cả lab dùng .claude/settings.local.json để tránh commit nhầm hook thử nghiệm. Nếu team muốn dùng chung, review xong mới copy phần ổn định sang .claude/settings.json.
Bước 1: Kiểm tra trạng thái repo và tool cần có
Chạy trong thư mục gốc taskflow-ai:
git status --short
Lệnh này hiển thị working tree dạng ngắn. Output kỳ vọng là rỗng hoặc chỉ có file bạn hiểu rõ. Rủi ro: nếu repo có thay đổi của worker khác, việc tạo hook hoặc format sau edit có thể làm diff chồng lên công việc của họ.
Chạy trong thư mục gốc taskflow-ai:
claude --version
Lệnh này in version Claude Code đang dùng. Output kỳ vọng là version hiện tại của Claude Code CLI. Rủi ro thấp; nếu version quá cũ, field như if có thể không hoạt động như tài liệu mới, nên script hook phải tự kiểm tra input.
Chạy trong thư mục gốc taskflow-ai:
jq --version
Lệnh này kiểm tra jq, tool dùng để đọc JSON input của hook. Output kỳ vọng giống jq-1.6 hoặc jq-1.7. Rủi ro thấp; nếu chưa có jq, đừng để Claude tự cài bằng command global, hãy cài theo quy trình máy dev hoặc dùng script Node/PowerShell tương đương.
Bước 2: Tạo thư mục hook local
Chạy trong thư mục gốc taskflow-ai:
mkdir -p .claude/hooks .claude/logs
Lệnh này tạo thư mục chứa script hook và log local. Output kỳ vọng thường rỗng. Rủi ro: tạo thư mục trong repo; hãy đảm bảo .claude/logs/ không bị commit nếu log có metadata nhạy cảm.
Nếu dùng PowerShell thay vì Bash, chạy ở root taskflow-ai:
New-Item -ItemType Directory -Force .claude/hooks, .claude/logs
Lệnh này làm cùng việc trên Windows PowerShell. Output kỳ vọng là thông tin directory được tạo hoặc đã tồn tại. Rủi ro tương tự: không commit log local.
Bước 3: Tạo hook chặn rm -rf
Tạo file taskflow-ai/.claude/hooks/block-rm-rf.sh bằng editor hoặc yêu cầu Claude Code tạo file đúng nội dung sau:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')"
if [[ "$COMMAND" =~ (^|[[:space:];\|\&])rm[[:space:]] ]] && \
{ [[ "$COMMAND" =~ -[A-Za-z]*r[A-Za-z]*f ]] || \
[[ "$COMMAND" =~ -[A-Za-z]*f[A-Za-z]*r ]] || \
{ [[ "$COMMAND" =~ -[A-Za-z]*r ]] && [[ "$COMMAND" =~ -[A-Za-z]*f ]]; }; }; then
echo "Blocked by taskflow-ai hook: rm -rf style command is not allowed. Use a narrow, reviewed cleanup command instead." >&2
exit 2
fi
exit 0
File path: taskflow-ai/.claude/hooks/block-rm-rf.sh.
Mục đích: đọc command Claude Code định chạy, phát hiện command dạng rm -rf, rm -fr, rm -r -f hoặc biến thể flag gộp, rồi block bằng exit 2.
Cách test: sau khi cấu hình settings, yêu cầu Claude Code chạy command giả lập nguy hiểm trong repo sandbox. Hook phải trả stderr "Blocked by taskflow-ai hook..." và tool call không được thực thi.
Edge cases: script này không phát hiện mọi command destructive, ví dụ find . -delete, python -c '...', alias shell hoặc script tự xóa file. Vì vậy vẫn cần permission deny, human review và rule không chạy cleanup rộng.
Chạy trong thư mục gốc taskflow-ai:
chmod +x .claude/hooks/block-rm-rf.sh
Lệnh này cấp quyền execute cho script hook trên Unix/macOS/Linux/Git Bash. Output kỳ vọng rỗng. Rủi ro thấp, nhưng không làm script an toàn hơn; quyền execute chỉ cho phép Claude Code chạy script.
Tạo hoặc cập nhật taskflow-ai/.claude/settings.local.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: đăng ký hook trước Bash tool. matcher: "Bash" lọc theo tool; if: "Bash(rm *)" lọc thêm theo command; script là lớp kiểm tra thật.
Cách test: chạy claude --debug-file .claude/logs/hooks-debug.log ở root repo, sau đó yêu cầu Claude chạy một command an toàn và một command rm -rf tmp. Xem debug log để xác nhận hook nào matched.
Edge cases: đây là JSON, không được có comment hoặc trailing comma. Dấu nháy trong command phải escape đúng. Nếu file JSON sai, Claude Code có thể không load settings hoặc báo lỗi hook.
Chạy trong thư mục gốc taskflow-ai:
jq empty .claude/settings.local.json
Lệnh này validate JSON settings. Output kỳ vọng rỗng và exit code 0. Rủi ro thấp; nếu fail, sửa JSON trước khi mở Claude Code.
Bước 4: Tạo hook formatter sau edit
Tạo file taskflow-ai/.claude/hooks/format-after-edit.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
if [[ -z "$FILE_PATH" ]]; then
exit 0
fi
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.md|*.css)
;;
*)
exit 0
;;
esac
cd "$PROJECT_DIR"
if [[ ! -f "$FILE_PATH" ]]; then
exit 0
fi
if [[ -x "node_modules/.bin/prettier" ]]; then
"node_modules/.bin/prettier" --write "$FILE_PATH" >/dev/null
echo "Formatted $FILE_PATH with local Prettier"
else
echo "Skipped formatter: local node_modules/.bin/prettier not found" >&2
fi
File path: taskflow-ai/.claude/hooks/format-after-edit.sh.
Mục đích: sau khi Claude Code sửa một file, format đúng file đó nếu extension nằm trong allowlist và project đã có local Prettier.
Cách test: yêu cầu Claude Code sửa một file .ts nhỏ, rồi chạy git diff để xem file đã format. Kiểm tra .claude/logs/hooks-debug.log nếu hook không chạy.
Edge cases: script cố tình không chạy npx prettier vì npx có thể download package nếu dependency chưa có. Script cũng không chạy npm run format toàn repo để tránh sửa nhiều file ngoài scope.
Chạy trong thư mục gốc taskflow-ai:
chmod +x .claude/hooks/format-after-edit.sh
Lệnh này cấp quyền execute cho formatter hook. Output kỳ vọng rỗng. Rủi ro: hook sẽ có quyền chạy formatter local, nên script phải giới hạn extension và file path.
Cập nhật taskflow-ai/.claude/settings.local.json để thêm PostToolUse:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh",
"args": [],
"timeout": 20
}
]
}
]
}
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: chạy formatter sau khi Claude Code dùng tool sửa file.
Cách test: validate JSON bằng jq empty .claude/settings.local.json, mở Claude Code, yêu cầu chỉnh một file .ts, kiểm tra debug log và diff.
Edge cases: PostToolUse xảy ra sau edit, nên formatter có thể tạo diff bổ sung. Nếu formatter làm thay đổi quá nhiều, dừng và đổi script để chỉ format file vừa sửa hoặc disable hook.
Bước 5: Tạo hook ghi log Bash command
Tạo file taskflow-ai/.claude/hooks/log-bash-command.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
LOG_DIR="$PROJECT_DIR/.claude/logs"
LOG_FILE="$LOG_DIR/command-log.jsonl"
mkdir -p "$LOG_DIR"
COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')"
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')"
CWD_VALUE="$(printf '%s' "$INPUT" | jq -r '.cwd // ""')"
EVENT_NAME="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // ""')"
REDACTED_COMMAND="$(printf '%s' "$COMMAND" | sed -E 's/((api[_-]?key|token|password|passwd|secret|authorization)=)[^[:space:]]+/\1<redacted>/Ig')"
jq -nc \
--arg ts "$(date -Iseconds)" \
--arg session_id "$SESSION_ID" \
--arg cwd "$CWD_VALUE" \
--arg event "$EVENT_NAME" \
--arg command "$REDACTED_COMMAND" \
'{ts:$ts, session_id:$session_id, cwd:$cwd, event:$event, command:$command}' \
>> "$LOG_FILE"
exit 0
File path: taskflow-ai/.claude/hooks/log-bash-command.sh.
Mục đích: append một dòng JSONL cho mỗi Bash tool call sau khi chạy xong, đủ audit nhưng không log stdout/stderr hoặc full transcript.
Cách test: mở Claude Code, yêu cầu chạy pwd hoặc git status --short, sau đó xem .claude/logs/command-log.jsonl.
Edge cases: redaction bằng regex không hoàn hảo. Không log toàn bộ prompt, .env, header, cookie hoặc transcript. Nếu command chứa secret ở dạng khác, log vẫn có thể leak; vì vậy log file phải local và không commit.
Chạy trong thư mục gốc taskflow-ai:
chmod +x .claude/hooks/log-bash-command.sh
Lệnh này cấp quyền execute cho log hook. Output kỳ vọng rỗng. Rủi ro: hook có quyền ghi log vào repo; cần .gitignore hoặc quy ước không commit log.
Cập nhật PostToolUse để có cả formatter và logger:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh",
"args": [],
"timeout": 20
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/log-bash-command.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: kết hợp 3 guardrail local: block trước Bash nguy hiểm, format sau edit, log sau Bash.
Cách test: validate JSON, mở Claude Code với debug file, chạy command an toàn, sửa file nhỏ và thử command bị block.
Edge cases: thứ tự hook trong cùng event có thể ảnh hưởng workflow nếu script này phụ thuộc output script khác. Không thiết kế hook phụ thuộc nhau trừ khi thật cần.
Chạy trong thư mục gốc taskflow-ai:
jq empty .claude/settings.local.json
Lệnh này validate JSON sau khi merge nhiều hook. Output kỳ vọng rỗng. Rủi ro thấp; nếu JSON lỗi, Claude Code không load đúng hook.
Bước 6: Mở Claude Code với debug log
Chạy trong thư mục gốc taskflow-ai:
claude --debug-file .claude/logs/hooks-debug.log
Lệnh này mở Claude Code và ghi debug log vào file local. Output kỳ vọng là Claude Code session sẵn sàng. Rủi ro: debug log có thể chứa command, path, stderr/stdout của hook; không commit hoặc chia sẻ nếu có dữ liệu nhạy cảm.
Trong Claude Code, dùng slash command:
/hooks
Lệnh slash này hiển thị/cấu hình hook đang load trong Claude Code. Output kỳ vọng là danh sách hook theo event hoặc UI quản lý hook. Rủi ro thấp, nhưng nếu nhiều scope cùng khai báo hook, cần xem kỹ hook đến từ file nào.
Prompt test command an toàn:
Hãy chạy `pwd` và `git status --short` trong repo taskflow-ai. Không sửa file.
Sau đó tóm tắt command đã chạy.
Kỳ vọng: command chạy bình thường, log-bash-command.sh ghi log.
Prompt test block:
Hãy thử chạy command `rm -rf tmp/day-12-hook-test` để xác nhận hook safety.
Nếu bị block, hãy giải thích vì sao và đề xuất command cleanup an toàn hơn nhưng chưa chạy.
Kỳ vọng: PreToolUse hook block tool call, Claude nhận feedback từ stderr. Rủi ro: không dùng path thật có dữ liệu; dù hook đang chặn, test command vẫn nên dùng path sandbox.
Prompt test formatter:
Hãy tạo hoặc sửa một file TypeScript nhỏ trong phạm vi branch hiện tại để kiểm tra formatter hook.
Chỉ sửa file test/sandbox nếu project đã có, không đụng business logic.
Sau khi edit, báo file đã sửa và không commit.
Kỳ vọng: PostToolUse hook chạy formatter cho file vừa sửa. Rủi ro: formatter có thể làm đổi style nhiều dòng nếu file đang chưa format; review git diff trước khi giữ lại.
Bước 7: Debug khi hook làm workflow khó hiểu
Chạy trong thư mục gốc taskflow-ai:
tail -n 80 .claude/logs/hooks-debug.log
Lệnh này xem 80 dòng cuối debug log. Output kỳ vọng có event, matcher, command hook, exit code, stdout/stderr. Rủi ro: log có thể chứa thông tin nhạy cảm; không paste toàn bộ lên chat nếu chưa redact.
Chạy trong thư mục gốc taskflow-ai:
tail -n 20 .claude/logs/command-log.jsonl
Lệnh này xem 20 command gần nhất mà Bash log hook ghi. Output kỳ vọng là JSONL gồm ts, session_id, cwd, event, command. Rủi ro: command có thể chứa secret nếu redaction không bắt được; kiểm tra trước khi chia sẻ.
Nếu cần disable toàn bộ hook local tạm thời, tạo hoặc sửa taskflow-ai/.claude/settings.local.json:
{
"disableAllHooks": true
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: tắt toàn bộ hook trong scope local để khôi phục workflow khi hook block nhầm hoặc timeout liên tục.
Cách test: mở lại Claude Code và chạy /hooks; danh sách hook local không còn thực thi.
Edge cases: nếu project shared hoặc managed settings cũng có hook, cần kiểm tra precedence bằng /hooks và debug log. Khi bật lại, xóa disableAllHooks hoặc restore settings trước đó.
Không dùng command destructive để "debug hook", ví dụ:
git reset --hard
git clean -fd
rm -rf .claude
docker compose down -v
Các lệnh này chạy ở root repo hoặc môi trường dev và có thể xóa thay đổi của bạn, worker khác, hook config hoặc database volume. Output kỳ vọng không quan trọng vì không nên chạy trong bài này. Nếu cần rollback, rollback từng file đã review.
5. Prompt mẫu nên dùng
Prompt khám phá hook hiện có
Bạn đang ở repo taskflow-ai. Hãy khảo sát cấu hình Claude Code hook hiện có.
Ràng buộc:
- Chỉ đọc file, chưa sửa.
- Kiểm tra các file .claude/settings.json và .claude/settings.local.json nếu tồn tại.
- Liệt kê event, matcher, command, timeout, scope và rủi ro từng hook.
- Không đọc hoặc in secret, .env, token, command log đầy đủ.
- Nếu thấy hook destructive hoặc matcher quá rộng, phân loại Blocker/Should fix/Nice to have.
Prompt lập plan tạo hook safety
Lập plan tạo hook local cho taskflow-ai để:
1. Block rm -rf trước khi Bash tool chạy.
2. Format file sau Edit/Write/MultiEdit.
3. Log Bash command đã redacted.
Yêu cầu:
- Dùng .claude/settings.local.json, chưa promote sang settings.json.
- Mỗi hook có file script riêng trong .claude/hooks.
- Matcher hẹp, timeout rõ, script tự validate JSON input.
- Không chạy command destructive, không format toàn repo, không log secret.
- Chờ tôi approve trước khi tạo hoặc sửa file.
Prompt implement hook
Implement hook theo plan đã duyệt.
Giới hạn:
- Chỉ tạo/sửa file trong .claude/hooks và .claude/settings.local.json.
- Không sửa README, source business logic, package.json, lockfile hoặc .env.
- Không chạy npm install, git add, git commit, git reset, git clean, rm, docker volume command hoặc migration.
- Mọi script phải set -euo pipefail, đọc JSON từ stdin, dùng fallback khi field thiếu.
- Log không được chứa secret, token, cookie, authorization header, password hoặc raw prompt.
- Sau khi xong, báo file đã tạo, hook event/matcher/timeout và command test an toàn.
Prompt review hook
Review diff hook hiện tại như senior developer, không sửa file.
Tập trung:
- Scope có đúng local/project/user không.
- Matcher có quá rộng không.
- JSON escaping trong settings có đúng không.
- timeout có hợp lý không.
- Hook có chạy command destructive hoặc format toàn repo không.
- Log có thể leak secret không.
- Có cách debug/disable rõ không.
Kết luận theo format: Blocker, Should fix, Nice to have, Test gaps.
Prompt test hook
Hãy giúp tôi test hook Day 12 bằng các bước an toàn.
Yêu cầu:
- Trước hết kiểm tra /hooks và debug log.
- Chạy command an toàn như pwd hoặc git status --short.
- Thử command rm -rf chỉ trên path sandbox và kỳ vọng bị block.
- Sửa một file sandbox nhỏ để kiểm tra formatter.
- Không commit, không xóa file, không chạy cleanup destructive.
- Nếu test fail, phân tích nguyên nhân trước, chưa sửa.
6. Trade-offs
Hook giúp automation nhất quán hơn prompt vì nó chạy tự động tại đúng event. Nhưng chính vì tự động, hook cũng dễ tạo side effect không ai nhớ đã bật. Một formatter hook chạy sau mọi edit có thể tiết kiệm thời gian, nhưng nếu nó format cả repo, reviewer sẽ thấy diff lớn và khó phân biệt thay đổi logic với thay đổi style.
PreToolUse là nơi tốt nhất để block hành động nguy hiểm. Trade-off là block nhầm làm Claude Code bị kẹt. Vì vậy matcher phải hẹp, message lỗi phải actionable, và luôn có cách disable/debug.
PostToolUse phù hợp cho log và formatter vì tool đã chạy. Trade-off là không thể ngăn hành động đã xảy ra. Nếu bạn dùng PostToolUse để phát hiện command nguy hiểm, bạn đã quá muộn.
UserPromptSubmit hữu ích để chặn prompt có secret hoặc thêm context nhỏ. Trade-off là prompt của user có thể chứa dữ liệu nhạy cảm; nếu hook log nguyên prompt, bạn vừa tạo thêm điểm rò rỉ.
SessionStart tiện để nạp context hoặc ghi audit, nhưng không nên chạy setup nặng. Một hook start session mà gọi dependency install, build hoặc database introspection sẽ làm mọi session chậm và khó phân biệt lỗi môi trường với lỗi Claude Code.
Project shared hooks giúp team có guardrail đồng nhất, nhưng yêu cầu review kỹ hơn local hook. Local hook nhanh để thử nghiệm, nhưng không bảo vệ đồng đội. User global hook tiện cho thói quen cá nhân, nhưng có thể phá repo khác nếu matcher hoặc path quá project-specific.
7. Best practices
- Dùng
.claude/settings.local.jsoncho lab và thử nghiệm; chỉ commit.claude/settings.jsonkhi script đã được review như production code. - Matcher càng hẹp càng tốt. Tránh
"*"nếu không có lý do rõ. - Dùng
ifđể giảm số lần hook chạy, nhưng script vẫn phải tự validate input. - Luôn đặt
timeoutngắn theo use case. Hook block/log không nên chạy quá vài giây. - Tách script ra file riêng thay vì nhồi pipeline dài trong JSON settings; JSON escaping sai là nguồn lỗi phổ biến.
- Validate settings bằng
jq empty .claude/settings.local.json. - Script hook phải chịu được field thiếu: dùng
.tool_input.command // "",.tool_input.file_path // empty. - Không log secret, token, cookie, authorization header, password, raw
.env, raw prompt hoặc full transcript. - Không chạy command destructive trong hook: không
rm, khônggit reset, khônggit clean, khôngdocker compose down -v, không migration destructive. - Formatter hook chỉ format file vừa sửa và extension trong allowlist. Không format toàn repo sau mỗi edit.
- Log hook chỉ append metadata đã redacted. Log file phải local, không commit.
- Message block phải nói rõ vì sao bị block và gợi ý cách làm an toàn hơn.
- Có runbook debug:
/hooks,claude --debug,claude --debug-file, kiểm tra exit code/stdout/stderr, disable bằngdisableAllHooks. - Hook không thay thế permission. Với command chắc chắn nguy hiểm, dùng cả permission
denyvà hook nếu cần rule động. - Review hook script như review code security: input parsing, quoting, path traversal, command injection, timeout, log retention.
8. Performance / cost / context
Hook không trực tiếp tiêu token như prompt dài, nhưng output của hook có thể đi vào transcript hoặc context tùy event. Nếu hook in quá nhiều stdout, Claude có thêm noise và session khó đọc. Với UserPromptSubmit và SessionStart, stdout có thể trở thành context cho Claude; vì vậy chỉ output nội dung ngắn, có giá trị.
Hook ảnh hưởng performance theo thời gian chạy:
PreToolUsechạy trước tool nên làm mọi command matched bị delay.PostToolUsechạy sau edit nên formatter chậm sẽ làm Claude Code có vẻ "đơ".UserPromptSubmitchạy trước mỗi prompt nên logic phức tạp làm mọi lượt chat chậm.SessionStartchạy khi mở/resume session nên command nặng làm startup chậm.
Cách tối ưu:
- Dùng matcher và
ifhẹp để hook không chạy vô ích. - Dùng script local, deterministic, không gọi network.
- Formatter chỉ chạy trên file vừa sửa; tránh
npm run formattoàn repo. - Log append JSONL đơn giản, không parse transcript lớn.
- Timeout ngắn; nếu hook cần xử lý dài, cân nhắc chuyển sang job ngoài Claude Code thay vì blocking hook.
- Giữ stdout ngắn; log chi tiết vào file local nếu cần debug.
- Không để hook tạo output chứa diff dài hoặc test log dài; yêu cầu Claude tự chạy test có chủ đích khi cần.
Chi phí maintainability cũng đáng kể. Hook là automation ẩn, nên phải có tài liệu trong CLAUDE.md hoặc docs team: hook nào tồn tại, vì sao, cách debug, cách disable, ai owner. Nếu không, developer sẽ mất thời gian tìm vì sao Claude Code không chạy được command tưởng như bình thường.
9. Checklist cuối bài
- Tôi hiểu hook chạy theo event và nhận JSON input qua
stdin. - Tôi phân biệt được
PreToolUse,PostToolUse,UserPromptSubmit,SessionStart. - Tôi biết chọn scope user/project/local và ưu tiên
.claude/settings.local.jsonkhi thử nghiệm. - Tôi đã tạo hook chặn
rm -rfbằngPreToolUsevới matcher/if hẹp và exit2. - Tôi đã tạo hook formatter sau edit, chỉ format file vừa sửa và extension allowlist.
- Tôi đã tạo hook log Bash command đã redacted vào file local.
- Tôi đã validate JSON settings bằng
jq empty. - Tôi đã đặt timeout cho từng hook.
- Tôi không log secret, prompt raw,
.env, token, cookie, authorization header hoặc full transcript. - Tôi biết dùng
/hooks,claude --debug-file, hook log vàdisableAllHooksđể debug/disable. - Tôi không chạy command destructive trong hook.
- Tôi đã review diff và chắc chắn không chạm file ngoài phạm vi hook.
10. Bài tập
Bài cơ bản: trong taskflow-ai, tạo local hook PreToolUse chặn rm -rf. Dùng .claude/settings.local.json, script riêng trong .claude/hooks, timeout 5 giây. Test bằng command an toàn và command sandbox bị block. Nộp settings, script, debug log đã redacted và nhận xét vì sao PostToolUse không phù hợp để chặn command này.
Bài nâng cao: tạo PostToolUse formatter hook cho Edit|Write|MultiEdit. Script chỉ format extension .ts, .tsx, .js, .jsx, .json, .md, .css và chỉ dùng local node_modules/.bin/prettier nếu có. Nộp diff trước/sau khi Claude sửa một file sandbox, giải thích cách tránh formatter làm đổi cả repo.
Bài áp dụng team: tạo PostToolUse log hook cho Bash command. Log vào .claude/logs/command-log.jsonl, redacted token/password/secret, không log stdout/stderr/prompt/transcript. Nộp 3 dòng log mẫu đã kiểm tra không chứa secret, và đề xuất rule .gitignore hoặc retention.
Bài reflection: viết runbook 10 dòng cho team taskflow-ai: hook nào đang bật, scope, owner, cách debug, cách disable, command bị cấm, chính sách log, khi nào promote từ local sang project shared. Yêu cầu Claude Code review runbook ở chế độ read-only trước khi đưa vào team docs.
Tài liệu
Tóm tắt kiến thức
Hook trong Claude Code là automation chạy theo event trong session. Hook nhận JSON input qua stdin, đọc các field như session_id, cwd, hook_event_name, tool_name, tool_input.command, tool_input.file_path, rồi quyết định log, format, thêm context hoặc block hành động.
Day 12 tập trung vào 4 event:
PreToolUse: chạy trước tool. Dùng để block risky command, bảo vệ file nhạy cảm, yêu cầu policy check. Đây là nơi đúng để chặnrm -rf.PostToolUse: chạy sau tool. Dùng để format file vừa sửa, log activity, phân tích output. Không dùng để ngăn hành động đã xảy ra.UserPromptSubmit: chạy khi user submit prompt, trước khi Claude xử lý. Dùng để chặn prompt chứa secret hoặc thêm context ngắn. Không log raw prompt.SessionStart: chạy khi session start/resume/clear/compact theo matcher. Dùng để nạp context an toàn hoặc ghi audit nhẹ.
Các điểm cần nhớ:
- Dùng đúng scope:
~/.claude/settings.jsoncho user,taskflow-ai/.claude/settings.jsoncho team shared,taskflow-ai/.claude/settings.local.jsoncho lab/local. - Hook command nên nằm ở file script riêng trong
.claude/hooksđể tránh JSON escaping phức tạp. matcherphải hẹp. VớiPreToolUse/PostToolUse, matcher thường làBash,Edit|Write|MultiEdit.ifgiúp filter thêm theo tool input, ví dụBash(rm *), nhưng script vẫn phải tự validate.- Exit
0nghĩa là hook thành công; exit2là blocking error ở event hỗ trợ block nhưPreToolUse; nếu dùng JSON control thì hook phải exit0. VớiPreToolUse, JSON control hiện tại nằm tronghookSpecificOutput.permissionDecision; vớiUserPromptSubmit, dùng{ "decision": "block", "reason": "..." }để block prompt rõ ràng. - Luôn set
timeout. Hook block/log nên rất ngắn; formatter chỉ format file vừa sửa. - Timeout mặc định trong docs hiện tại: command/HTTP/MCP hook thường là
600giây; riêngUserPromptSubmithạ default của các loại này xuống30giây; prompt hook là30giây; agent hook là60giây. Đây là fallback, không phải best practice cho repo team. - Log không được chứa secret, token, cookie, authorization header, raw prompt,
.env, transcript hoặc stdout/stderr dài. - Hook không được chạy command destructive như
rm,git reset,git clean,docker compose down -v, migration destructive.
Sơ đồ tư duy hoặc luồng xử lý
User prompt / Claude action
|
v
Claude Code event
|
+-- UserPromptSubmit
| |
| +-- kiểm tra prompt có secret?
| +-- thêm context ngắn nếu cần
|
+-- SessionStart
| |
| +-- ghi session start
| +-- nạp context an toàn
|
+-- PreToolUse
| |
| +-- matcher tool: Bash, Edit, Write...
| +-- if filter: Bash(rm *), Edit(*.ts)...
| +-- script đọc JSON stdin
| +-- exit 2 -> block tool call
| +-- exit 0 -> cho tool chạy
|
+-- Tool executes
|
+-- PostToolUse
|
+-- formatter file vừa sửa
+-- log Bash command đã redacted
+-- feedback ngắn cho Claude
Luồng triển khai hook an toàn:
Xác định use case
|
v
Chọn event đúng
|
v
Chọn scope local trước
|
v
Viết matcher hẹp + timeout
|
v
Tách script riêng trong .claude/hooks
|
v
Script đọc JSON, validate field, không destructive
|
v
jq empty settings.local.json
|
v
claude --debug-file .claude/logs/hooks-debug.log
|
v
Test command an toàn
|
v
Review diff, logs, secret leakage
|
+-- ổn định -> cân nhắc promote sang .claude/settings.json
|
+-- lỗi/block nhầm -> disableAllHooks, sửa matcher/script
Bảng so sánh
| Event | Chạy trước/sau | Có thể block trước hành động? | Input quan trọng | Use case tốt | Rủi ro chính |
|---|---|---|---|---|---|
PreToolUse | Trước tool | Có, dùng exit 2 | tool_name, tool_input | Block rm -rf, bảo vệ .env, policy check | Block nhầm, matcher rộng làm chậm mọi tool |
PostToolUse | Sau tool | Không ngăn được hành động đã xảy ra | tool_name, tool_input, tool result tùy schema | Format file, log command, feedback sau edit | Formatter tạo diff lớn, log lộ secret |
UserPromptSubmit | Trước Claude xử lý prompt | Có thể block prompt | prompt/user input và session metadata | Chặn secret trong prompt, thêm context ngắn | Log raw prompt, làm mọi lượt chat chậm |
SessionStart | Khi start/resume | Không phải event tool | source/session metadata | Nạp context, ghi audit start | Startup chậm, context noise |
| Scope | File | Nên chứa | Không nên chứa |
|---|---|---|---|
| User | ~/.claude/settings.json | Rule cá nhân, path user-local | Hook phụ thuộc project taskflow-ai |
| Project shared | .claude/settings.json | Guardrail team đã review | Secret, path máy cá nhân, log local |
| Project local | .claude/settings.local.json | Lab, thử nghiệm, local override | Policy duy nhất team dựa vào |
| Managed | Managed settings | Policy tổ chức | Experiment chưa review |
| Hook use case | Event nên dùng | Matcher gợi ý | Timeout gợi ý | Ghi chú |
|---|---|---|---|---|
Chặn rm -rf | PreToolUse | Bash + if: Bash(rm *) | 5 giây | Dùng cả permission deny nếu command pattern rõ |
| Formatter sau edit | PostToolUse | `Edit | Write | MultiEdit` |
| Log command | PostToolUse | Bash | 3-5 giây | Redact secret, append JSONL local |
| Chặn prompt chứa secret | UserPromptSubmit | Event này thường không cần matcher tool | 3-5 giây | Không log raw prompt |
| Nạp context đầu session | SessionStart | `startup | resume | clear |
| Chủ đề safety | Nên làm | Không nên làm |
|---|---|---|
| Matcher | Hẹp theo tool và command/file pattern | Dùng "*" cho mọi event |
| JSON | Validate bằng jq empty, escape quote trong command | Nhồi pipeline dài có nhiều quote vào settings |
| Script | set -euo pipefail, fallback field thiếu, quote path | Tin rằng field luôn tồn tại |
| Formatter | Chỉ format file vừa sửa, extension allowlist | Chạy npm run format toàn repo sau mọi edit |
| Logging | Log metadata đã redacted vào .claude/logs | Log .env, prompt, transcript, stdout/stderr dài |
| Command | Đọc input, append log, chạy formatter local | rm, git reset, git clean, volume delete, migration destructive |
| Debug | /hooks, claude --debug-file, hook log, exit code | Xóa cả .claude hoặc reset repo để hết lỗi |
Lỗi thường gặp
Dùng
PostToolUseđể chặn command nguy hiểm
Lúc này command đã chạy. Cách sửa: chuyển policy block sangPreToolUse.Matcher quá rộng
Ví dụ matcher"*"làm script chạy trên mọi tool. Workflow chậm và debug khó. Cách sửa: dùngBash,Edit|Write|MultiEdit, hoặcifhẹp hơn.Script phụ thuộc field luôn tồn tại
jq -r '.tool_input.command'có thể trảnullvới tool không phải Bash. Cách sửa: dùng.tool_input.command // ""hoặc.tool_input.file_path // empty.JSON escaping sai trong settings
Command có quote hoặc path có khoảng trắng dễ làm JSON invalid. Cách sửa: tách script riêng, dùng exec form"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/script.sh"kèm"args": [], validate bằngjq empty.Formatter hook format toàn repo
Sau một edit nhỏ, diff biến thành hàng trăm file. Cách sửa: chỉ formattool_input.file_pathvà extension allowlist.Log chứa secret
Bash command có thể chứaTOKEN=..., header, password, cookie. Cách sửa: redact trước khi ghi log, không log stdout/stderr/prompt/transcript, đưa.claude/logs/vào ignore.Hook gọi network hoặc service ngoài
Network chậm hoặc fail làm Claude Code session kẹt. Cách sửa: hook phải local, deterministic; việc nặng chuyển sang command user chủ động chạy.Không có timeout
Hook treo làm Claude Code treo theo. Cách sửa: settimeoutcho từng hook, thường3-30giây tùy use case.Không biết hook đến từ scope nào
User, project, local, managed settings có thể cùng tồn tại. Cách sửa: dùng/hooksvà debug log để xem hook matched và command nào chạy.Disable bằng cách xóa bừa file
Xóa.claudehoặc reset repo có thể mất rule team hoặc thay đổi của người khác. Cách sửa: dùngdisableAllHookstrong local settings hoặc tắt bằng/hooksnếu phù hợp.
Cách debug
Kiểm tra settings JSON. Chạy ở root taskflow-ai:
jq empty .claude/settings.local.json
Lệnh này validate JSON local settings. Output kỳ vọng rỗng, exit code 0. Rủi ro thấp; nếu file không tồn tại, command báo lỗi file missing.
Mở Claude Code với debug file. Chạy ở root taskflow-ai:
claude --debug-file .claude/logs/hooks-debug.log
Lệnh này ghi log debug hook vào file local. Output kỳ vọng là session Claude Code sẵn sàng. Rủi ro: debug log có thể chứa command, path, stdout/stderr; không commit hoặc paste raw log.
Trong Claude Code, chạy:
/hooks
Slash command này hiển thị hook đang cấu hình. Output kỳ vọng cho thấy event, matcher, command và scope. Rủi ro thấp; cần kiểm tra kỹ nếu nhiều scope cùng có hook.
Xem debug log. Chạy ở root taskflow-ai:
tail -n 80 .claude/logs/hooks-debug.log
Lệnh này xem 80 dòng cuối debug log. Output kỳ vọng có event, hook command, timeout, exit status, stdout/stderr. Rủi ro: log có thể chứa dữ liệu nhạy cảm; redact trước khi đưa vào prompt.
Xem command log. Chạy ở root taskflow-ai:
tail -n 20 .claude/logs/command-log.jsonl
Lệnh này xem 20 dòng log Bash gần nhất. Output kỳ vọng là JSONL đã redacted. Rủi ro: nếu redaction chưa đủ, log vẫn có thể chứa secret; không commit.
Kiểm tra hook script có executable. Chạy ở root taskflow-ai:
ls -l .claude/hooks
Lệnh này liệt kê file script và permission. Output kỳ vọng script .sh có quyền execute trên Unix-like shell. Rủi ro thấp; trên Windows PowerShell, permission execute có thể không phản ánh giống Linux.
Disable toàn bộ hook local khi bị kẹt bằng settings:
{
"disableAllHooks": true
}
File path: taskflow-ai/.claude/settings.local.json.
Mục đích: tắt hook trong scope local để tiếp tục làm việc và debug. Cách test: mở lại Claude Code, chạy /hooks, xác nhận hook không còn execute. Edge case: hook ở project shared/managed có thể vẫn tồn tại tùy precedence, cần xem debug log.
Prompt debug nên dùng:
Hook đang block workflow. Hãy phân tích từ settings và debug log đã redacted.
Ràng buộc:
- Chưa sửa file.
- Xác định hook nào matched, event nào, command nào, exit code nào.
- Phân loại lỗi: JSON invalid, matcher sai, script bug, timeout, permission, secret redaction, hay logic block nhầm.
- Đề xuất patch nhỏ nhất.
Link tài liệu nên đọc
- Claude Code Hooks: https://code.claude.com/docs/en/hooks
- Claude Code Hooks Guide: https://code.claude.com/docs/en/hooks-guide
- Claude Code Settings /
.claudedirectory: https://code.claude.com/docs/en/claude-directory - Claude Code Permissions: https://code.claude.com/docs/en/permissions
- Claude Code Troubleshooting: https://code.claude.com/docs/en/troubleshooting
- jq Manual: https://jqlang.github.io/jq/manual/
- Git Ignore documentation: https://git-scm.com/docs/gitignore
- OWASP Logging Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html
Bài tập
Bài 1 — Cơ bản
Mục tiêu: tạo PreToolUse hook local để chặn rm -rf trong project taskflow-ai.
Yêu cầu:
Mở terminal tại thư mục gốc
taskflow-ai.Kiểm tra working tree:
git status --short
Lệnh này chạy ở root repo để xem file đang thay đổi. Output kỳ vọng là rỗng hoặc chỉ có file bạn hiểu rõ. Rủi ro: nếu có thay đổi của worker khác, đừng để hook formatter/logging làm nhiễu diff của họ.
- Kiểm tra Claude Code và
jq:
claude --version
Lệnh này chạy ở root repo hoặc terminal bất kỳ để xem version Claude Code. Output kỳ vọng là version hiện tại. Rủi ro thấp; nếu version cũ, kiểm tra kỹ field if.
jq --version
Lệnh này kiểm tra tool parse JSON cho hook script. Output kỳ vọng là jq-.... Rủi ro thấp; nếu thiếu jq, dùng script Node/PowerShell thay thế hoặc cài theo quy trình máy dev, không để Claude tự chạy cài đặt global.
- Tạo thư mục hook local:
mkdir -p .claude/hooks .claude/logs
Lệnh này chạy ở root taskflow-ai để tạo nơi đặt script và log. Output kỳ vọng rỗng. Rủi ro: .claude/logs không được commit nếu chứa log thật.
- Tạo file
taskflow-ai/.claude/hooks/block-rm-rf.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')"
if [[ "$COMMAND" =~ (^|[[:space:];\|\&])rm[[:space:]] ]] && \
{ [[ "$COMMAND" =~ -[A-Za-z]*r[A-Za-z]*f ]] || \
[[ "$COMMAND" =~ -[A-Za-z]*f[A-Za-z]*r ]] || \
{ [[ "$COMMAND" =~ -[A-Za-z]*r ]] && [[ "$COMMAND" =~ -[A-Za-z]*f ]]; }; }; then
echo "Blocked by taskflow-ai hook: rm -rf style command is not allowed. Use a narrow, reviewed cleanup command instead." >&2
exit 2
fi
exit 0
File path: .claude/hooks/block-rm-rf.sh.
Mục đích: block command dạng rm -rf bằng exit 2 trước khi Bash tool chạy.
Cách test: dùng Claude Code thử command sandbox có rm -rf; hook phải block. Không test trên path thật có dữ liệu.
Edge cases: không bắt mọi command destructive như find -delete; đây là guardrail bổ sung, không thay thế review và permission deny.
- Cấp quyền execute:
chmod +x .claude/hooks/block-rm-rf.sh
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro thấp; nếu dùng Windows thuần không có Bash, cần PowerShell script tương đương.
- Tạo
taskflow-ai/.claude/settings.local.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: .claude/settings.local.json.
Mục đích: cấu hình hook local cho repo, không commit vào team config.
Cách test: validate JSON rồi mở Claude Code với debug log.
Edge cases: JSON không có comment/trailing comma; dùng ${CLAUDE_PROJECT_DIR} và args: [] để tránh lỗi shell quoting khi project path có khoảng trắng.
- Validate settings:
jq empty .claude/settings.local.json
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro thấp; nếu có lỗi, sửa JSON trước khi mở Claude Code.
- Mở Claude Code với debug:
claude --debug-file .claude/logs/hooks-debug.log
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng là session Claude Code sẵn sàng. Rủi ro: debug log có thể chứa command/path; không commit.
- Trong Claude Code, test bằng prompt:
Hãy chạy `pwd` để xác nhận command an toàn vẫn chạy.
Sau đó thử chạy `rm -rf tmp/day-12-hook-test` để xác nhận hook block command nguy hiểm.
Không sửa file và không chạy cleanup khác.
Kết quả cần nộp: nội dung settings, nội dung script, đoạn debug log đã redacted chứng minh hook matched, và giải thích vì sao exit 2 được dùng.
Bài 2 — Thực tế
Mục tiêu: tạo PostToolUse hook chạy formatter sau khi Claude Code sửa file trong taskflow-ai.
Yêu cầu:
- Từ trạng thái sau Bài 1, tạo file
taskflow-ai/.claude/hooks/format-after-edit.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
if [[ -z "$FILE_PATH" ]]; then
exit 0
fi
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.md|*.css)
;;
*)
exit 0
;;
esac
cd "$PROJECT_DIR"
if [[ ! -f "$FILE_PATH" ]]; then
exit 0
fi
if [[ -x "node_modules/.bin/prettier" ]]; then
"node_modules/.bin/prettier" --write "$FILE_PATH" >/dev/null
echo "Formatted $FILE_PATH with local Prettier"
else
echo "Skipped formatter: local node_modules/.bin/prettier not found" >&2
fi
File path: .claude/hooks/format-after-edit.sh.
Mục đích: format file vừa được Claude Code sửa, chỉ với extension allowlist, chỉ dùng local Prettier.
Cách test: cho Claude Code sửa một file sandbox .ts hoặc .md, rồi kiểm tra diff.
Edge cases: nếu node_modules/.bin/prettier chưa tồn tại, hook skip thay vì dùng npx để tránh download bất ngờ.
- Cấp quyền execute:
chmod +x .claude/hooks/format-after-edit.sh
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro: hook sẽ chạy formatter tự động, nên phải giữ script hẹp.
- Cập nhật
.claude/settings.local.jsonđể có thêmPostToolUse:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh",
"args": [],
"timeout": 20
}
]
}
]
}
}
File path: .claude/settings.local.json.
Mục đích: chạy formatter sau edit/write/multiedit.
Cách test: validate JSON và dùng /hooks để xem event PostToolUse.
Edge cases: PostToolUse không undo edit; nếu formatter gây diff lớn, phải rollback theo file hoặc disable hook rồi sửa.
- Validate settings:
jq empty .claude/settings.local.json
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro thấp.
- Mở Claude Code:
claude --debug-file .claude/logs/hooks-debug.log
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng là session sẵn sàng. Rủi ro: debug log có thể chứa file path và hook stderr.
- Gửi prompt test formatter:
Hãy tạo hoặc sửa một file sandbox nhỏ để kiểm tra formatter hook.
Ràng buộc:
- Chỉ dùng file trong test/sandbox hoặc thư mục tạm đã tồn tại trong taskflow-ai.
- Nếu chưa có nơi sandbox, hãy đề xuất path trước, chưa tạo.
- Không sửa business logic.
- Sau edit, báo file đã sửa, hook formatter có chạy không, và command test/read-only tôi nên chạy.
- Review diff:
git diff --stat
Lệnh này chạy ở root taskflow-ai để xem phạm vi thay đổi. Output kỳ vọng chỉ có file sandbox và hook config/script. Rủi ro: --stat không cho thấy logic, chỉ phát hiện patch rộng bất thường.
git diff
Lệnh này chạy ở root taskflow-ai để xem patch chi tiết. Output kỳ vọng không có business logic bị chạm ngoài bài. Rủi ro: diff có thể chứa log hoặc formatting noise; không commit log.
Kết quả cần nộp: settings đã cập nhật, script formatter, diff summary và phân tích vì sao hook không format toàn repo.
Bài 3 — Nâng cao
Mục tiêu: tạo PostToolUse hook log Bash command đã redacted, phục vụ audit/debug mà không leak secret.
Yêu cầu:
- Tạo file
taskflow-ai/.claude/hooks/log-bash-command.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
LOG_DIR="$PROJECT_DIR/.claude/logs"
LOG_FILE="$LOG_DIR/command-log.jsonl"
mkdir -p "$LOG_DIR"
COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')"
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')"
CWD_VALUE="$(printf '%s' "$INPUT" | jq -r '.cwd // ""')"
EVENT_NAME="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // ""')"
REDACTED_COMMAND="$(printf '%s' "$COMMAND" | sed -E 's/((api[_-]?key|token|password|passwd|secret|authorization)=)[^[:space:]]+/\1<redacted>/Ig')"
jq -nc \
--arg ts "$(date -Iseconds)" \
--arg session_id "$SESSION_ID" \
--arg cwd "$CWD_VALUE" \
--arg event "$EVENT_NAME" \
--arg command "$REDACTED_COMMAND" \
'{ts:$ts, session_id:$session_id, cwd:$cwd, event:$event, command:$command}' \
>> "$LOG_FILE"
exit 0
File path: .claude/hooks/log-bash-command.sh.
Mục đích: log metadata của Bash tool call dưới dạng JSONL. Không log stdout/stderr/prompt/transcript.
Cách test: chạy command an toàn qua Claude Code, sau đó xem .claude/logs/command-log.jsonl.
Edge cases: regex redaction không hoàn hảo; command có secret dạng khác vẫn có thể leak. Không commit log.
- Cấp quyền execute:
chmod +x .claude/hooks/log-bash-command.sh
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro: hook ghi log local; cần kiểm soát .gitignore và log retention.
- Cập nhật
.claude/settings.local.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm-rf.sh",
"args": [],
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh",
"args": [],
"timeout": 20
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/log-bash-command.sh",
"args": [],
"timeout": 5
}
]
}
]
}
}
File path: .claude/settings.local.json.
Mục đích: kết hợp block hook, formatter hook và command log hook.
Cách test: validate JSON, mở Claude Code debug, chạy command an toàn.
Edge cases: nhiều hook cùng event không nên phụ thuộc lẫn nhau; mỗi hook phải tự đủ dữ liệu và fail an toàn.
- Validate settings:
jq empty .claude/settings.local.json
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng rỗng. Rủi ro thấp.
- Test logging bằng Claude Code:
Hãy chạy `pwd` và `git status --short`.
Không sửa file.
Sau đó cho biết command nào đã chạy.
- Xem log:
tail -n 5 .claude/logs/command-log.jsonl
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng là 5 dòng JSONL gần nhất, có ts, session_id, cwd, event, command. Rủi ro: log có thể chứa secret nếu redaction chưa đủ; kiểm tra và redact trước khi nộp.
- Test redaction bằng prompt an toàn:
Hãy mô phỏng một command echo chứa token giả: `echo token=fake-value-for-redaction-test`.
Không dùng token thật.
Sau đó kiểm tra log command có redacted không.
Kỳ vọng: log command chứa token=<redacted> hoặc ít nhất không chứa secret thật. Rủi ro: không bao giờ dùng secret thật để test redaction.
Kết quả cần nộp: script logger, settings, 2-3 dòng log mẫu đã redacted, và chính sách không commit .claude/logs/.
Bài 4 — Review & Reflection
Mục tiêu: review hook như code production và viết runbook debug cho team taskflow-ai.
Yêu cầu:
- Yêu cầu Claude Code review hook read-only:
Review hook Day 12 hiện tại như senior developer, không sửa file.
Tập trung:
- Hook nào thuộc PreToolUse/PostToolUse/UserPromptSubmit/SessionStart nếu có.
- Scope local/project/user có phù hợp không.
- Matcher và if có quá rộng không.
- JSON escaping có đúng không.
- timeout có hợp lý không.
- Script có thể leak secret không.
- Hook có chạy command destructive hoặc format toàn repo không.
- Có cách debug/disable rõ không.
Kết luận theo format: Blocker, Should fix, Nice to have, Test gaps.
- Kiểm tra debug log:
tail -n 80 .claude/logs/hooks-debug.log
Lệnh này chạy ở root taskflow-ai để xem hook gần nhất. Output kỳ vọng có hook command, exit status, stdout/stderr. Rủi ro: log có thể chứa thông tin nhạy cảm; redact trước khi đưa vào Claude.
- Kiểm tra command log:
tail -n 20 .claude/logs/command-log.jsonl
Lệnh này chạy ở root taskflow-ai. Output kỳ vọng là JSONL đã redacted. Rủi ro: nếu thấy secret, dừng lại, xóa log local theo quy trình an toàn và sửa redaction trước khi dùng tiếp.
- Viết runbook ngắn:
Dựa trên hook Day 12, hãy viết runbook 10-15 dòng cho team taskflow-ai.
Runbook phải có:
- Hook đang bật và scope.
- Owner hoặc nơi review.
- Cách test an toàn.
- Cách debug bằng /hooks và claude --debug-file.
- Cách disable bằng disableAllHooks.
- Command bị cấm.
- Chính sách log không chứa secret.
- Khi nào được promote từ settings.local.json sang settings.json.
- Thử disable local hook bằng cách thay
.claude/settings.local.jsonthành:
{
"disableAllHooks": true
}
File path: .claude/settings.local.json.
Mục đích: xác nhận bạn có đường thoát khi hook block nhầm. Cách test: mở lại Claude Code, chạy /hooks, xác nhận hook local không execute. Edge cases: đừng commit settings này nếu team cần hook shared.
- Restore settings hook sau khi test disable bằng editor hoặc Git theo file nếu file đã tracked. Nếu dùng Git rollback, chạy ở root
taskflow-aivà chỉ với file bạn sở hữu:
git restore -- .claude/settings.local.json
Lệnh này rollback tracked local settings về trạng thái Git. Output kỳ vọng rỗng. Rủi ro: mất thay đổi chưa commit trong file đó; không dùng nếu file có thay đổi của người khác hoặc chưa tracked.
Kết quả cần nộp: review findings, runbook, bằng chứng biết disable/debug hook, và quyết định có promote hook nào sang project shared không.
Tiêu chí hoàn thành
- Đã tạo hook
PreToolUsechặnrm -rfbằng script riêng, matcher/if hẹp, timeout5giây. - Đã tạo hook
PostToolUseformatter chỉ format file vừa sửa và extension allowlist. - Đã tạo hook
PostToolUselog Bash command đã redacted vào.claude/logs/command-log.jsonl. - Dùng
.claude/settings.local.jsoncho lab, không tự promote sang.claude/settings.json. - Settings JSON validate được bằng
jq empty. - Hook script đọc JSON từ
stdin, dùng fallback khi field thiếu. - Không hook nào chạy command destructive, install dependency, migration, reset Git hoặc format toàn repo.
- Log không chứa secret thật, prompt raw,
.env, stdout/stderr dài hoặc transcript. - Đã test bằng command an toàn và command sandbox bị block.
- Biết debug bằng
/hooks,claude --debug-file,hooks-debug.log,command-log.jsonl. - Biết disable bằng
disableAllHooksvà hiểu rủi ro khi disable. - Có review read-only theo Blocker/Should fix/Nice to have/Test gaps.
Gợi ý nếu bí
Nếu Claude Code không nhận hook:
Hãy kiểm tra vì sao hook không load.
Chỉ đọc file.
Kiểm tra .claude/settings.local.json có valid JSON không, path command có đúng không, script có executable không, matcher có khớp event không.
Không sửa file cho đến khi phân loại nguyên nhân.
Nếu JSON settings lỗi:
jq empty .claude/settings.local.json
Lệnh này chạy ở root taskflow-ai để chỉ ra lỗi JSON. Output kỳ vọng khi lỗi là line/column. Rủi ro thấp; sửa JSON rồi chạy lại.
Nếu formatter không chạy:
Formatter hook không chạy. Hãy phân tích debug log đã redacted.
Kiểm tra:
- PostToolUse có matched Edit/Write/MultiEdit không.
- tool_input.file_path có tồn tại không.
- file extension có nằm trong allowlist không.
- node_modules/.bin/prettier có tồn tại không.
- hook có timeout hoặc permission error không.
Chưa sửa file.
Nếu log có secret:
Command log có nguy cơ chứa secret. Hãy đề xuất patch redaction tối thiểu.
Không in lại secret.
Không đọc .env.
Không sửa file cho đến khi tôi approve.
Nếu hook block nhầm:
Hook block nhầm command an toàn. Hãy phân tích matcher, if và script regex.
Chưa sửa file.
Đề xuất test cases cho command nên block và command nên allow.
Nếu workflow bị kẹt:
{
"disableAllHooks": true
}
Đặt tạm trong .claude/settings.local.json, mở lại Claude Code, dùng /hooks kiểm tra. Rủi ro: tắt cả guardrail local; chỉ dùng để debug và bật lại sau.
Đáp án tham khảo hoặc expected result
Kết quả tốt cho Bài 1:
.claude/settings.local.jsoncóPreToolUsematcherBash,if: Bash(rm *), command trỏ tớiblock-rm-rf.shbằng${CLAUDE_PROJECT_DIR}, cóargs: [], timeout5.- Script đọc
.tool_input.command // "", phát hiệnrm -rfstyle, ghi message rõ ràng vào stderr vàexit 2. - Command
pwdchạy bình thường. - Command sandbox
rm -rf tmp/day-12-hook-testbị block trước khi thực thi.
Ví dụ debug log mong đợi đã redacted:
[DEBUG] Executing hooks for PreToolUse:Bash
[DEBUG] Executing hook command: <project>/.claude/hooks/block-rm-rf.sh with timeout 5000ms
[DEBUG] Hook command completed with status 2
Kết quả tốt cho Bài 2:
.claude/settings.local.jsoncóPostToolUsematcherEdit|Write|MultiEdit.format-after-edit.shchỉ format extension allowlist và chỉ dùng localnode_modules/.bin/prettier.- Nếu local Prettier không có, hook skip và báo stderr ngắn; không chạy
npxdownload package. git diff --statkhông xuất hiện hàng loạt file do format toàn repo.
Kết quả tốt cho Bài 3:
log-bash-command.shghi JSONL vào.claude/logs/command-log.jsonl.- Mỗi dòng có
ts,session_id,cwd,event,command. - Command có
token=fake-value-for-redaction-testđược redacted. - Log không chứa stdout/stderr, prompt raw,
.env, transcript hoặc secret thật.
Ví dụ log mẫu:
{"ts":"2026-05-14T10:20:30+07:00","session_id":"abc123","cwd":"/path/to/taskflow-ai","event":"PostToolUse","command":"git status --short"}
{"ts":"2026-05-14T10:21:10+07:00","session_id":"abc123","cwd":"/path/to/taskflow-ai","event":"PostToolUse","command":"echo token=<redacted>"}
Kết quả tốt cho Bài 4:
- Review phát hiện được ít nhất các rủi ro: matcher rộng, JSON escaping, timeout, formatter toàn repo, log leak secret, thiếu disable path.
- Runbook có cách debug bằng
/hooksvàclaude --debug-file. - Biết dùng
disableAllHooksnhưng hiểu đây là giải pháp tạm. - Có quyết định rõ:
block-rm-rfcó thể promote sang.claude/settings.jsonsau team review.- formatter hook nên giữ local nếu team chưa thống nhất formatter.
- command log hook nên giữ local hoặc có policy retention/redaction rõ trước khi share.