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:

CaseMô tả
Happy pathInput đầy đủ, mọi thứ OK
Missing dependencyTool cần thiết không có (vd direnv chưa cài)
Invalid argArg truyền vào sai format
Partial stateFile cần tạo đã tồn tại với nội dung cũ
CancellationUser 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 --oneline là 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
  1. 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).*