TL;DR và kết luận
Bốn việc bạn cứ gõ vào Claude Code mỗi việc thuộc về một file khác nhau. Checklist phát hành là một skill. Quy tắc không bao giờ được phá là một hook PreToolUse. Cuộc kiểm tra đọc nhiều file là một subagent. Dữ liệu Claude không nhìn thấy là một MCP server.
Chúng tôi chạy cả bốn trên một repo thật, một API hóa đơn với 34 test, và lần nào cũng chọn sai trước. Các lựa chọn sai đều thất bại ngay trên màn hình:
- Checklist trong CLAUDE.md thêm một dòng
Memory files: 210 tokensvào /context, ở mỗi lượt. - Hook đầu tiên chạy, in cảnh báo, và thao tác ghi vẫn lọt qua (unitPrice 8000 thành 12000).
- Cuộc kiểm tra chạy inline làm ngập thread: Messages ở 19.3k token, so với 7.2k khi dùng subagent được gọi tên.
- Không có server docs, Claude trả lời "I couldn't find that setting... I won't guess one."
Kết luận: chọn loại từ bảng của docs, rồi làm cho file trả lời được, chứ không chỉ tồn tại. Một hook chỉ ghi log chỉ mang tính khuyến nghị, giống dòng CLAUDE.md mà nó thay thế. Trong bốn loại, chỉ hook là không bao giờ hỏi.
Hãy sao chép các file bên dưới, commit chúng, và bắt đầu từ cái bạn gõ lại nhiều nhất.
Tổng hợp có nguồn
Phiên của bạn nạp gì trước chữ đầu tiên. Baseline /context của chúng tôi hiện 26.2k/1m token, không có dòng memory và không có dòng MCP. Mỗi việc bạn thêm hoặc đặt một dòng vào bảng đó hoặc không. Hãy đọc nó trước, rồi đo so với nó.
Quy tắc trong CLAUDE.md là một lời đề nghị, hook là sự thực thi. Docs nói rằng một chỉ dẫn như "never edit .env" trong CLAUDE.md hoặc trong một skill là một lời đề nghị, còn một hook PreToolUse chặn thao tác chỉnh sửa là sự thực thi s1. Trang Memory gọi CLAUDE.md là context, không phải sự thực thi s8. Một thread trên Hacker News cũng báo cáo điều tương tự từ phía hook: một hook thoát với mã 0 không chặn gì cả s10.
Bảng kích hoạt cho bạn biết dùng file nào. Dán một quy trình lần thứ ba nghĩa là một skill. Output làm ngập cuộc trò chuyện nghĩa là một subagent. Một tab Claude không nhìn thấy nghĩa là một MCP server. Thứ phải xảy ra mỗi lần nghĩa là một hook s1.
Việc 1, checklist phát hành, là một skill.
- Một quy trình đã phình ra bên trong CLAUDE.md thuộc về một skill, và nội dung của nó chỉ được nạp khi dùng s3.
- Trong lần chạy của chúng tôi, checklist dán vào CLAUDE.md đã thêm một dòng
Memory files: 210 tokens. Khi là skill vớidisable-model-invocation: true, /context trở về baseline. /releasechạy sáu bước (34 test, 0.4.0 lên 0.4.1, tag v0.4.1). Nó còn phát hiệngit commit -ambỏ qua các file chưa được theo dõi, nên đã stage changelog mới trước.
Việc 2, quy tắc phải giữ, là một hook, và sự đảm bảo là câu trả lời nó trả về.
- Thoát với mã 0 mà không có output là không có quyết định nào. Thoát với mã 2 thì chặn. JSON deny cũng chặn s2.
- Hook đầu tiên của chúng tôi ghi log, thoát với mã 0 và thao tác ghi lọt qua (unitPrice 8000 thành 12000). Câu trả lời của Claude không hề nhắc đến hook.
- Cùng hook đó khi trả về JSON deny đã từ chối thao tác chỉnh sửa với
PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).Sau đó Claude hỏi: bỏ qua, hay viết migration. - Docs công bố một công thức PreToolUse cho các file được bảo vệ s23.
- Giới hạn: hook này theo dõi Edit và Write. Hãy bao cả shell, hoặc giữ shell ngoài danh sách allow của bạn.
onFailure: "block"làm một hook thất bại chặn thay vì fail open. Nó xuất hiện ở v2.1.295 s2 s15.
Việc 3, cuộc kiểm tra gây ngập, là một subagent.
- Một subagent bắt đầu mà không biết gì và trả về một bản tóm tắt s4.
- Inline, Messages hiện 19.3k. Với subagent được gọi tên, nó hiện 7.2k.
- Chỉ riêng file agent đã ủy quyền 0 trên 2 lần chạy. Gọi tên nó trong prompt thì ủy quyền xảy ra. File làm nó tồn tại, prompt của bạn làm Claude dùng nó.
- Hãy ghim
model:trong file. Hóa đơn không đổi: việc đọc vẫn xảy ra, chỉ là không nằm trong cửa sổ của bạn.
Việc 4, dữ liệu Claude không với tới, là một MCP server.
claude mcp add --scope projectghi ra.mcp.json, và tool search chỉ nạp tên công cụ s5.- Có một server HTTP miễn phí, không cần đăng nhập, cho docs của chính Claude Code, với
search_claude_code_docsvàquery_docs_filesystem_claude_code_docss24. - Không có nó, Claude trả lời "I couldn't find that setting... I won't guess one." Có nó, Claude trả lời
"onFailure": "block", được thêm ở v2.1.295, trong khi bản cài đặt của chúng tôi là 2.1.294. - Một server docs cho thư viện mà model đã biết không thay đổi gì với chúng tôi. Hãy kết nối thứ model không thể có.
Báo cáo thực địa. Một thread trên Reddit báo cáo Claude phớt lờ một dòng "MUST use agent" trong CLAUDE.md s9. Chúng tôi trích nó như một báo cáo, không phải một phép đo. Các bài viết kỹ thuật của Anthropic bàn về cùng sự phân chia này s6 s7.
Code
Bốn file từ lần chạy, cộng hai file hỗ trợ. Đường dẫn là nơi mỗi file nằm trong repo của bạn.
1. Skill release, tại .claude/skills/release/SKILL.md. Gõ /release. disable-model-invocation: true giữ quyền khởi chạy cho riêng bạn, đúng thứ bạn muốn cho một việc có commit và tag.
SKILL.md
---
name: release
description: Cut a patch release of ledgerly (typecheck, tests, version bump, changelog line, commit, tag).
disable-model-invocation: true
---
Run these steps in order and stop at the first failure:
1. `pnpm typecheck` must exit 0.
2. `pnpm test` must pass.
3. Bump the patch version in `package.json` (e.g. 0.4.0 -> 0.4.1).
4. Add one line at the top of `CHANGELOG.md` (create it if missing): `## vX.Y.Z - YYYY-MM-DD` followed by a one-line summary of the commits since the last tag.
5. `git commit -am "release vX.Y.Z"`
6. `git tag vX.Y.Z`. Never push.
2. Hook bảo vệ dữ liệu, trong .claude/settings.json. Nó trả về quyết định deny kèm một lý do mà Claude đọc. Nó cần jq trong PATH của bạn và nằm gọn inline, nên hook không cần file script riêng. Hãy đổi mẫu data/.*\.json$ và lý do cho khớp với quy tắc của bạn.
settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
3. Conventions auditor, tại .claude/agents/conventions-auditor.md. Các công cụ được giới hạn ở loại chỉ đọc và model được ghim. Hãy sửa danh sách quy ước cho khớp với README của bạn.
conventions-auditor.md
---
name: conventions-auditor
description: Use when asked to audit the code against the README conventions
model: sonnet
tools: Read, Grep, Glob
---
You audit ledgerly's source against README § Conventions:
- Money is always an integer number of cents. Never floats.
- Domain functions return new objects; they never mutate their input.
- Validation failures throw `ValidationError` (400), missing records `NotFoundError` (404), illegal state transitions `ConflictError` (409).
- A change of stored shape bumps `schemaVersion` and adds a migration in `src/store/migrations.ts`.
Read every file under `src/`. Return only the findings: one line per violation as `file:line - convention - what is wrong`, then borderline cases the same way, then one line with the count of files read. No file contents, no summary of clean files.
conventions-auditor.md on GitHub
4. Cấu hình MCP, lưu thành .mcp.json ở gốc repo. Đó là thứ mà claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp ghi ra. Claude Code liệt kê một server của dự án ở trạng thái pending cho đến khi bạn chấp thuận trong phiên kế tiếp.
mcp.json
{
"mcpServers": {
"claude-docs": {
"type": "http",
"url": "https://code.claude.com/docs/mcp"
}
}
}
File hỗ trợ. ctx.sh chạy /context trong một phiên headless và in các dòng token, để bạn so sánh trước và sau. mcp-probe.sh gửi initialize và tools/list tới một url MCP HTTP, để kiểm tra server có trả lời trước khi bạn thêm nó.
ctx.sh
#!/usr/bin/env bash
# Usage: ctx.sh <step> -> runs "/context" through run-session.sh, writes context-<step>.txt (honours STRICT_MCP / EXTRA_ALLOW)
C="$(cd "$(dirname "$0")" && pwd)"
"$C/run-session.sh" "$C/out/context-$1.json" "/context" >/dev/null
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['result'])" "$C/out/context-$1.json" > "$C/context-$1.txt"
grep -E '^\*\*Tokens|^\| (System prompt|System tools|MCP tools|Memory files|Skills|Custom agents|Messages) ' "$C/context-$1.txt"
mcp-probe.sh
#!/usr/bin/env bash
# Usage: mcp-probe.sh <url> [tools/call json params] -> initialize + notifications/initialized + tools/list (+ optional tools/call) over streamable HTTP
U="$1"; H=$(mktemp)
curl -s -D "$H" -m 30 -X POST "$U" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
echo; SID=$(grep -i '^mcp-session-id:' "$H" | tr -d '\r' | cut -d' ' -f2); echo "session-id: ${SID:-none}"
HS=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream'); [ -n "$SID" ] && HS+=(-H "Mcp-Session-Id: $SID")
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; echo
[ -n "${2:-}" ] && { curl -s -m 90 -X POST "$U" "${HS[@]}" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":$2}"; echo; }
rm -f "$H"
Làm vào thứ Hai
- Mở một phiên mới và chạy /context. Ghi lại tổng. Đó là baseline của bạn.
- Chọn checklist bạn dán nhiều nhất. Chuyển nó vào
.claude/skills/<name>/SKILL.md, thêmdisable-model-invocation: truenếu nó commit hoặc tag, và chạy bằng/<name>. - Lấy một quy tắc không bao giờ được phá và đặt vào một hook PreToolUse trả về
permissionDecision: "deny"hoặc thoát với mã 2. Yêu cầu Claude phá quy tắc, và kiểm tra thao tác chỉnh sửa bị từ chối. - Nếu hook của bạn chỉ ghi log, hãy đổi nó. Một hook thoát với mã 0 không chặn gì cả.
- Chuyển một việc đọc nhiều file vào
.claude/agents/<name>.mdvớimodel:được ghim. Hãy gọi tên nó trong prompt cho đến khi Claude tự chọn nó. - Kiểm tra một sự thật mà Claude đã đoán cho bạn gần đây. Nếu nó mới hơn model, hãy thêm một server docs bằng
claude mcp add --transport http <name> --scope project <url>và commit.mcp.json. - Kiểm tra version Claude Code của bạn trước khi dựa vào một setting.
onFailure: "block"cần 2.1.295. - Chạy
git add -A && git statusvà commit các file mới. Lần chạy của chúng tôi hiện đúng bốn file.
Đi xa hơn
Thẻ chọn. Một trang. Đây là các hàng của docs s1 với bốn việc của chúng tôi được ghi lên đó. Phần chính của gói này là bốn file ở trên cộng với tấm thẻ này.
| Loại | Nó làm gì | Dùng khi | Việc của chúng tôi | File của chúng tôi |
|---|---|---|---|---|
| CLAUDE.md | Context lâu dài được nạp ở mọi cuộc trò chuyện | Một quy ước Claude làm sai hai lần | Một dòng, không phải một quy trình | không có |
| Skill | Chỉ dẫn và quy trình Claude có thể dùng | Bạn dán cùng một playbook lần thứ ba | Checklist phát hành | .claude/skills/release/SKILL.md |
| Hook | Một script được kích hoạt bởi sự kiện | Bạn muốn nó xảy ra mỗi lần, không cần hỏi | Không bao giờ sửa dữ liệu lưu trữ bằng tay | .claude/settings.json |
| Subagent | Context tách biệt trả về kết quả đã tóm tắt | Một việc phụ làm ngập cuộc trò chuyện của bạn | Cuộc kiểm tra quy ước | .claude/agents/conventions-auditor.md |
| MCP | Kết nối tới dịch vụ bên ngoài | Bạn cứ sao chép dữ liệu từ một tab Claude không nhìn thấy | Docs của Claude Code | .mcp.json |
| Plugin | Gói bốn loại thành một đơn vị cài đặt được | Một repository thứ hai cần cùng thiết lập | Không dùng | không có |
Tấm thẻ quyết định loại, không quyết định chất lượng. Một hook chỉ ghi log, một subagent bạn không bao giờ gọi tên và một server cho docs mà model biết đều nằm đúng loại mà vẫn thất bại. Hãy chọn loại, rồi làm cho file trả lời được.
Khi nào không nên dùng những thứ này.
- Một quy ước Claude cứ làm sai vẫn nằm trong CLAUDE.md dưới dạng một dòng, không phải một quy trình.
- Một việc bạn điều hướng qua lại, với lịch sử của bạn trong tầm nhìn, ở lại trong thread chính, không vào subagent.
- Một server docs cho thư viện mà model đã biết tốn context và không thay đổi gì.
pnpm testlà một CLI bạn đã có. Đó không phải việc của MCP.
Plugin. Plugin là chiếc hộp đóng gói cả bốn cùng nhau. Hãy dùng khi một repo khác cần cùng thiết lập.
Skill cộng MCP. Docs ghép chúng lại: server cung cấp kết nối, còn skill dạy Claude cách dùng nó cho tốt.
Skill với context: fork. Một skill có thể chạy trong một subagent được fork, để việc đọc của nó nằm ngoài cửa sổ của bạn. Hãy thử trên một việc trước khi dựa vào nó.
Các loại hook ngoài command. Command là loại chúng tôi đã dùng. Tài liệu tham chiếu hooks liệt kê các loại handler khác, gồm hook http và prompt. Quy tắc vẫn vậy: điều hook trả về quyết định, và một hook im lặng không quyết định gì.
MCP so với CLI. Đang có một cuộc tranh luận mở về việc MCP server ngốn context, với một bài viết ủng hộ dùng CLI trước s11 và một bài khác về chi phí context của MCP server s12. Quy tắc của chúng tôi: nếu một CLI đã làm được việc, hãy dùng nó. Dùng MCP cho thứ không có CLI. Tool search giờ chỉ nạp tên, nên hãy đo bằng /context trên thiết lập của chính bạn trước khi quyết định s5.
Giữ skill ngắn và đã được kiểm chứng. Mỗi việc một skill ngắn, đối chiếu với một lần chạy thật. Xem bài báo SkillsBench s13 và góc nhìn của một người thực hành về lý do skill tốn rất ít cho đến khi được dùng s14.
FAQ
Tại sao hook của tôi chạy mà thao tác chỉnh sửa vẫn lọt qua? Một hook chỉ quyết định thông qua những gì lệnh của nó trả về. Thoát với mã 0 mà không có output là không có quyết định nào s2. Hãy trả về JSON deny, hoặc thoát với mã 2.
Tại sao Claude tự đọc file khi tôi đã có subagent? Trong lần chạy của chúng tôi, chỉ riêng file agent đã ủy quyền 0 trên 2 lần chạy. Hãy gọi tên nó trong prompt, ví dụ "use the conventions auditor subagent".
Hook có bao hết mọi thứ Claude có thể làm không? Không. Hook của chúng tôi khớp với Edit và Write. Claude đã đề nghị thực hiện thay đổi qua shell trong một lần chạy sau, nên hãy bao cả shell hoặc giữ shell ngoài danh sách allow của bạn.
Tôi có nên thêm một MCP server cho mỗi thư viện tôi dùng không? Không. Nếu model đã biết thư viện đó, một server không thay đổi gì và tốn context. Hãy thêm cho dữ liệu mới hơn model hoặc nằm ngoài đĩa của bạn, và kiểm tra version đã cài so với version đã thêm tính năng.
Tôi có thể commit tất cả những thứ này không?
Có. Skill, file settings, file agent và .mcp.json đều nằm trong repo, nên ai clone nó cũng nhận được. Hãy chấp thuận MCP server của dự án một lần trong phiên kế tiếp.
AIDive