Bạn dùng Claude Code được vài tháng. Một số thao tác lặp đi lặp lại: mỗi khi tạo branch mới, bạn đều nhắc nó kiểm tra account gh trước, chạy direnv allow, cập nhật .gitignore. Tốn 3-4 turn mỗi lần. Bạn nghĩ: “viết skill cho nó đi”. Bạn tạo SKILL.md, nhét hết mọi thứ vào, và nhận ra skill không trigger đúng lúc, hoặc trigger nhầm hoàn cảnh, hoặc làm quá nhiều thứ cùng lúc.
Skill không phải macro. Một skill tốt có hành vi xác định, scope hẹp, xử lý được failure, và có lifecycle rõ ràng. Viết skill tệ còn tệ hơn không viết, vì nó tạo ảo giác tự động hóa trong khi thực ra đang tạo thêm confusion.
Bài này đi từng bước: từ câu hỏi “có nên build skill không”, qua 8 bước design, đến một walkthrough ví dụ cụ thể.
Khi nào nên build skill
Ba tiêu chí cần thỏa mãn cùng lúc:
1. Workflow lặp lại ít nhất 3 lần. Một lần là ngẫu nhiên. Hai lần là trùng hợp. Ba lần trở lên là pattern. Nếu bạn chưa làm nó 3 lần thì requirements chưa đủ rõ để viết skill.
2. Có thể mô tả deterministically. Skill là instruction cho model, không phải code imperative. Nếu bạn không thể viết ra “khi X xảy ra, làm Y, nếu Z thì làm W” thành câu văn rõ ràng, thì workflow đó chưa đủ well-defined để đóng gói.
3. Scope hẹp, một trách nhiệm. Skill làm được một việc tốt, không phải mười việc trung bình.
Anti-pattern phổ biến
Skill quá generic. Tên như /helper, /setup, /do-stuff là dấu hiệu rõ. Nếu bạn không thể mô tả skill làm gì trong một câu không dùng từ “và”, scope quá rộng.
Skill quá nhiều bước. Một skill dài 10+ bước thường là hai hoặc ba workflow riêng biệt bị nhồi chung. Ví dụ: /format-and-lint-and-test-and-deploy là bốn skill, không phải một. Mỗi cái có trigger riêng, failure riêng, và không phải lúc nào cũng chạy cùng nhau.
Skill duplicate công việc của hook. Nếu bạn muốn “mỗi khi commit thì chạy X”, đó là hook, không phải skill. Skill là user-triggered, hook là event-triggered. Nhầm hai cái này là nguyên nhân phổ biến nhất tạo ra skill không hoạt động như mong đợi.
8 bước build skill
Bước 1: Define trigger
Trigger nằm trong trường description của frontmatter YAML. Đây là phần model đọc để quyết định có activate skill này không. Viết sai phần này là skill không bao giờ được gọi, hoặc bị gọi nhầm lúc.
Trigger tốt có ba thành phần: ngữ cảnh kích hoạt, ví dụ câu lệnh cụ thể, và tình huống không dùng (optional nhưng giúp giảm false positive).
---
name: setup-envrc
description: "Set up .envrc with project environment variables. Use when user says
'setup envrc', 'add envrc', 'configure direnv for this repo'. Do NOT trigger on
general 'setup' or 'configure' requests unrelated to direnv."
---
Tránh description mơ hồ như "helps with environment setup". Model không có đủ thông tin để phân biệt đây là skill cần gọi hay chỉ là gợi ý.
Bước 2: Define scope
Trước khi viết body, viết ra một câu: “Skill này làm gì, và không làm gì.” Cái “không làm gì” quan trọng không kém.
Ví dụ cho skill direnv:
Làm: phát hiện account gh, tạo .envrc, chạy direnv allow, cập nhật .gitignore.
Không làm: cài direnv nếu chưa có (dừng lại và hướng dẫn user cài), thêm env var
ngoài GH_TOKEN (nếu cần mở rộng thì làm riêng), commit file .gitignore thay user.
Scope creep trong skill xảy ra dần. Bạn thêm “à tiện đây cũng check X” và skill trở thành god object. Ghi ra scope rõ trước giúp resist lực kéo đó.
Bước 3: Structure body
Body của skill là instruction cho model. Dùng section rõ ràng, numbered steps nếu workflow tuần tự.
+----------------------------+
| ## Phase 1: Preflight | <- check điều kiện cần
| ## Phase 2: Collect input | <- ask user nếu cần
| ## Phase 3: Apply | <- thực thi workflow
| ## Phase 4: Verify | <- confirm output
+----------------------------+
Mỗi phase là một H2. Bên trong phase, numbered list cho sequential steps. Bullet list cho song song hoặc no-order. Đừng trộn lẫn.
Dùng code block cho mọi command, template, snippet cụ thể. Model đọc skill như đọc documentation, nên format tốt giúp model follow chính xác hơn.
Bước 4: Arg handling
Skill có thể nhận argument sau slash command. Ví dụ /nf-memory personal truyền personal làm argument.
Khi skill nhận arg, body phải chỉ rõ:
- Arg ở đâu trong invocation (sau skill name, cách nhau bởi space)
- Validation rule: chấp nhận dạng nào, reject dạng nào
- Fallback khi thiếu arg: interactive picker, hay error?
## Argument handling
Skill có thể gọi với argument: `/setup-envrc personal` hoặc không có arg.
- **Có arg:** dùng arg đó làm account name. Validate: không chứa space, không empty.
Nếu invalid, dừng và show error message rõ.
- **Không có arg:** list tất cả `gh` accounts và hỏi user chọn. Không assume default.
Validate ở đầu, trước khi làm bất cứ gì. Tránh làm nửa workflow rồi mới phát hiện arg sai.
Bước 5: disable-model-invocation
Trường disable-model-invocation (hoặc tương đương tùy phiên bản CC) kiểm soát model có tự gọi skill hay không.
true: chỉ user có thể trigger bằng slash command. Model không tự gọi kể cả khi nó “thấy phù hợp”.false(default): model có thể tự invoke khi phát hiện ngữ cảnh phù hợp với description.
Dùng true khi skill có side effect (write file, chạy command, tạo resource). Tránh model tự kích hoạt một workflow có side effect mà user chưa biết. Dùng false khi skill thuần informational hoặc khi bạn muốn model chủ động suggest.
Bước 6: Sub-file trong skill folder
Skill folder không bị giới hạn ở một SKILL.md. Bạn có thể thêm:
skills/setup-envrc/
SKILL.md <- body chính
envrc.template <- template file
check-deps.sh <- helper script
notes.md <- internal reference
Trong SKILL.md, link đến sub-file bằng @<path> hoặc dùng Read tool để load nội dung. Tách ra khi:
- Template phức tạp (lặp lại nhiều, dễ sai nếu inline)
- Logic shell đủ dài để đáng có file riêng
- Reference doc cần đọc nhưng không cần in ra mỗi lần
Đừng tạo sub-file chỉ để trông “có tổ chức”. Nếu template chỉ 5 dòng, inline thẳng vào body.
Bước 7: Failure path
Một skill không có failure path là một skill chỉ hoạt động trong happy path. Production thì luôn có edge case.
Với mỗi step có thể fail, chỉ rõ:
- Condition fail là gì
- Dừng lại hay tiếp tục
- Message user nhận được là gì
- State cleanup nếu cần
## Error handling
- **direnv không được cài:** STOP. Không làm gì khác. Show message:
"direnv chưa được cài. Chạy `brew install direnv` trước."
- **Không phải git repo:** STOP. Show message: "Skill này chỉ chạy trong git repo."
- **gh auth token fail:** STOP. Show: "Không lấy được token. Chạy `gh auth login` trước."
- **direnv allow fail:** Tiếp tục nhưng warn user. File .envrc đã được tạo,
user tự chạy `direnv allow` khi tiện.
Phân biệt “STOP hard” và “warn + continue”. Fail liên quan đến điều kiện cần (tool không có, không có auth) thì hard stop. Fail ở step cuối (apply thành công rồi verify fail) có thể warn và cho user tự xử.
Bước 8: Test trước khi commit
Trước khi coi skill là production-ready, test tối thiểu 3-5 case:
| Case | Mô tả |
|---|---|
| Happy path | Input đầy đủ, mọi thứ OK |
| Missing dependency | Tool cần thiết không có (vd direnv chưa cài) |
| Invalid arg | Arg truyền vào sai format |
| Partial state | File cần tạo đã tồn tại với nội dung cũ |
| Cancellation | User cancel ở giữa workflow |
Test bằng cách chạy skill trong terminal thực với từng case. Không có cách nào tốt hơn. Model không chạy unit test cho skill của bạn.
Walkthrough: skill /log-today
Ví dụ cụ thể từ ý tưởng đến SKILL.md hoàn chỉnh.
Ý tưởng ban đầu: Mỗi buổi sáng tôi hay hỏi CC “hôm nay đã làm gì từ git log”. Lặp 3-4 lần tuần. Tôi muốn một skill gọn gàng cho việc này.
Áp tiêu chí:
- Lặp 3+ lần: đúng
- Deterministic: đúng,
git log --since midnight --onelinelà lệnh cố định - Scope hẹp: đúng, chỉ show git log của ngày hôm nay
Scope statement: Làm: lấy git log trong ngày, format gọn. Không làm: phân tích commit, gợi ý gì cần làm, tạo report.
Arg: Không cần. Ngày luôn là “hôm nay”.
Failure path: Không phải git repo thì hard stop. Không có commit nào thì show message “chưa có commit hôm nay” thay vì output rỗng.
SKILL.md kết quả:
---
name: log-today
description: "Show git commits made today in the current repo. Use when user asks
'what did I commit today', 'git log today', 'hôm nay commit gì'. Does NOT analyze
commits or suggest next actions."
---
# Log Today
Show all git commits made since midnight today in the current repo.
## Workflow
1. **Check git repo**: run `git rev-parse --is-inside-work-tree`
- If not a git repo: STOP. Tell user: "Skill này cần chạy trong git repo."
2. **Get today's commits:**
```bash
git log --since="midnight" --oneline --no-merges
- Format output:
- If commits found: show them in a numbered list with hash + message
- If no commits: show “Chưa có commit nào hôm nay.”
Rules
- NEVER analyze or comment on commit quality
- NEVER suggest what to do next
- Show output and stop
Gọn. Scope rõ. Failure path có. Không có gì thừa.
## Lifecycle maintenance
Skill không phải set-and-forget. Giống memory, skill stale theo thời gian.
Dấu hiệu skill cần review:
- Workflow bên dưới đã thay đổi (tool update, auth flow mới) nhưng skill chưa được cập nhật
- Skill không được gọi trong nhiều tháng: có thể workflow đó không còn relevant
- Có skill khác đang cover overlap: merge hoặc retire một trong hai
- Description không còn match với behavior thực tế
Review định kỳ, khoảng 3-6 tháng một lần, xem qua tất cả skill trong folder. Retire skill không dùng thay vì giữ cho có. Skill thừa không chỉ tốn chỗ trong folder mà còn làm description pool lớn hơn, tăng khả năng false positive trigger.
Cách retire: xóa folder hoặc thêm `disabled: true` trong frontmatter nếu muốn giữ lại làm reference.
## Tóm tắt và bài tiếp theo
- Build skill khi workflow lặp 3+ lần, mô tả được deterministically, và scope đủ hẹp.
- Anti-pattern: skill tên generic, skill overscoped, skill duplicate hook.
- 8 bước: trigger rõ, scope statement, body structured, arg validation, disable-model-invocation, sub-file khi cần, failure path, test 3-5 case.
- Lifecycle: review định kỳ, retire skill stale, đừng giữ skill không dùng.
Skill giải quyết được phần lớn automation cần thiết khi workflow là tuần tự và well-defined. Nhưng có những trường hợp skill không đủ: bạn muốn tự động chạy một lệnh trước mỗi commit, hoặc gửi notification khi session kết thúc. Đó là phần việc của hook, và bài 25 sẽ đi vào anatomy của 1 hook production-grade: khi nào dùng hook thay skill, viết hook shell script đúng cách, và các gotcha thường gặp.
---
*Bài thuộc series [Claude Code từ zero](/series/claude-code-tu-zero/). Series plan tại [bài giới thiệu](claude-code-tu-zero-series-plan).*