AIDive

Đừng Gõ Lại Prompt Claude Code: 4 Việc Thành 4 File

Bởi AIDive · Đăng ngày

Coding agentTự động hoá & workflow

Bốn prompt bạn gõ lại thành bốn file

Extension của Claude Code là những file mà một repo có thể chứa để bạn không phải gõ lại cùng một prompt: skill, hook, subagent và MCP server. Chúng tôi chạy bốn việc lặp đi lặp lại trên một repo thật, một API hóa đơn nhỏ dùng file JSON làm cơ sở dữ liệu. Mỗi việc lúc đầu được giao cho extension sai, và lựa chọn sai đều thất bại ngay trên màn hình. Sau đó mỗi việc trở thành một file được commit. Trang này trình bày bốn thao tác đó và tấm thẻ chọn quyết định nên dùng loại nào.

Hai kết quả bao trùm mọi thứ bên dưới. Hook thường được mô tả là một sự đảm bảo, còn quy tắc trong memory file là một lời đề nghị, vậy mà hook đầu tiên của chúng tôi chạy, in cảnh báo, và thao tác ghi vẫn lọt qua. Điều biến một hook chỉ chạy thành một hook thực sự chặn được nằm ở phần về hook. Server docs đầu tiên chúng tôi cắm vào không thay đổi gì, còn server trả lời được là server docs của chính công cụ. Công thức hook mà hầu hết mọi người sao chép còn cần một file script riêng, nhưng hook của chúng tôi nằm gọn trong file settings.

Trước khi viết bất kỳ file nào trong bốn file, bạn cần biết phiên của mình đã nạp sẵn những gì.

Bốn thứ đó là gì, và phiên của bạn đã nạp gì

Extension của Claude Code là một file bạn thêm vào repo để không phải gõ lại cùng những chỉ dẫn vào ô prompt. Có bốn loại, và ai đã biết rồi có thể nhảy sang phần tiếp theo.

  • Skill là một runbook được lưu trong repo. Claude chỉ nạp các bước của nó khi việc đó xuất hiện, và bạn có thể khởi chạy bằng cách gõ tên.
  • Hook giống như commit hook trong git, nhưng dành cho các hành động của chính Claude. Đó là một script chạy khi có sự kiện, chẳng hạn một lần chỉnh sửa, bất kể Claude định làm gì.
  • Subagent là một nhà thầu với chiếc bàn trống. Nó nhận bản brief của bạn, tự đọc, và gửi về một báo cáo thay vì cả đống tài liệu.
  • MCP server là một API client mà Claude có thể gọi, dành cho dữ liệu không nằm trên đĩa của bạn, như cơ sở dữ liệu hoặc docs của một công cụ.

Để đo bất cứ thứ gì, trước hết bạn cần một baseline. Mở một phiên mới và chạy lệnh context. Nó in ra một bảng mọi thứ đã được nạp trước chữ đầu tiên của bạn. Trên bench của chúng tôi, tổng cộng là 26,2k token trên cửa sổ 1m, không có dòng memory files và hoàn toàn không có dòng MCP. Từ giờ, mỗi việc bạn giao cho Claude hoặc thêm một dòng vào bảng đó, hoặc để nguyên nó.

Việc đầu: checklist phát hành thành skill

Skill là một checklist được lưu trong thư mục riêng mà Claude chỉ đọc khi bạn gọi nó. Việc đầu tiên là một checklist phát hành mà bạn cứ dán vào ô prompt: typecheck, test, tăng version, thêm một dòng changelog, commit và tag.

Chỗ hiển nhiên để đặt nó là memory file, và đó là lựa chọn sai, vì mọi thứ trong file đó được nạp ở mỗi lượt của mỗi phiên. Chúng tôi đặt các bước vào đó rồi đọc lại bảng context. Nó đã có thêm một dòng Memory files 210 token, được nạp dù bạn có đang phát hành hay không. Docs cũng nói đúng như vậy: khi một mục trong file đó đã phình thành một quy trình, nó thuộc về một skill.

Lựa chọn đúng là một thư mục dưới thư mục skills với các bước trong .claude/skills/release/SKILL.md, một mô tả ngắn ở đầu, và thêm một dòng frontmatter, disable-model-invocation: true. Dòng đó giữ skill ra khỏi danh sách của chính Claude, nên chỉ bạn mới khởi chạy được, đúng thứ bạn muốn cho một việc có commit và tag. Sau đó bạn gõ dấu gạch chéo và tên của nó, và checklist chạy.

Số đo context Tổng Dòng Memory files
Baseline, phiên mới 26.2k / 1m không có
Checklist trong memory file 26.4k / 1m 210 token
Checklist thành skill 26.2k / 1m không có, skill không được liệt kê

Khi chạy, Claude hoàn thành cả sáu bước trong chưa đầy một phút: typecheck, 34 test, tăng version từ 0.4.0 lên 0.4.1, dòng changelog, commit và tag. Nó còn phát hiện một lỗi trong checklist của chính chúng tôi. Lệnh commit như đã viết bỏ qua các file chưa được theo dõi, nên Claude đã stage changelog mới trước và nói rõ điều đó. Giờ việc phát hành là một lệnh bạn gõ, nó nằm trong repo cho cả nhóm, và không tốn gì trong context cho đến khi bạn gõ.

Việc hai: quy tắc phải giữ thành hook

Hook là một script mà Claude Code chạy trước khi một công cụ kích hoạt, ở đây là trước mọi thao tác chỉnh sửa hoặc ghi, và câu trả lời của script đó quyết định điều xảy ra tiếp theo. Việc thứ hai là một quy tắc không bao giờ được phá: trong repo này file dữ liệu lưu trữ không bao giờ được sửa bằng tay, và mọi thay đổi đều đi qua một migration.

Viết quy tắc đó bằng chữ in hoa trong memory file không làm nó có hiệu lực. Docs nói thẳng: file đó là context, không phải cấu hình có hiệu lực, và một chỉ dẫn như "never edit this file" là một lời đề nghị, không phải một sự đảm bảo, trong khi một hook chặn thao tác chỉnh sửa mới là sự thực thi.

Hook đầu tiên hiển nhiên là cái chúng tôi đã viết. Trong file settings, nó theo dõi các thao tác ghi vào file dữ liệu, in một cảnh báo, rồi thoát với mã không, như một script ghi log. Sau đó chúng tôi yêu cầu Claude đổi trực tiếp số tiền một hóa đơn trong file, không qua migration. Hook chạy và cảnh báo được in ra, nhưng thao tác ghi vẫn lọt qua: đơn giá của hóa đơn thứ ba đổi từ 8000 thành 12000. Câu trả lời của Claude không hề nhắc đến hook, vì một hook thoát sạch và không in gì thì chưa đưa ra quyết định nào, và các cảnh báo của nó không bao giờ đến được Claude.

Sự đảm bảo không nằm ở việc hook chạy, mà ở câu trả lời nó trả về. Cách sửa giữ nguyên hook và cho nó trả về một quyết định deny: một dòng JSON ngắn mang lý do bạn muốn Claude đọc. Thoát với mã hai cũng chặn theo cách tương tự. Toàn bộ hook là một lệnh inline trong .claude/settings.json, không có file script:

{
  "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'"
          }
        ]
      }
    ]
  }
}

Với cùng yêu cầu đó, thao tác chỉnh sửa bị từ chối và file dữ liệu không bị đụng đến. Claude thấy lỗi này: PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Nó nói rằng mình chưa thay đổi gì và hỏi nên bỏ qua rào chắn hay viết migration.

Giới hạn, do chính Claude nêu ra: hook này chỉ theo dõi các công cụ chỉnh sửa và ghi, và trong một lần chạy sau đó Claude đề nghị thực hiện thay đổi qua shell. Một hook phải bao cả shell, hoặc shell phải nằm ngoài danh sách allow của bạn, vì một rào chắn chỉ che một cánh cửa vẫn chỉ là một lời đề nghị. Hook không tốn gì trong context, vì nó chạy bên ngoài cuộc trò chuyện của bạn.

Việc ba: cuộc kiểm tra gây ngập thành subagent

Subagent là một phiên Claude riêng với context trống của riêng nó. Nó nhận brief của bạn, đọc những gì cần, và chỉ một bản tóm tắt được gửi về. Việc thứ ba là một cuộc kiểm tra làm ngập thread của bạn: đối chiếu mọi file dưới thư mục source với các quy ước trong README, và liệt kê mọi vi phạm kèm file và dòng.

Khi hỏi inline, Claude đọc mọi file vào cuộc trò chuyện của bạn, và toàn bộ nội dung đó nằm lại đó suốt phần còn lại của phiên. Trong lần chạy của chúng tôi Claude đọc cả 19 file source, dòng Messages của bảng context phình lên, và các phát hiện bị chôn dưới đống đó.

Lựa chọn đúng là một file dưới .claude/agents/, ở đây là .claude/agents/conventions-auditor.md, với tên, mô tả khi nào dùng, các công cụ được phép dùng, và một model được ghim. Ghim model là quan trọng, vì nếu không subagent sẽ thừa hưởng model mà phiên của bạn đang chạy, và hầu hết các việc đọc không cần model lớn.

Chúng tôi hỏi lại đúng câu hỏi đó, và Claude tự đọc các file. Cả hai lần nó đều không gọi agent, dù agent có trong danh sách: 0 trên 2 lần ủy quyền. Điều có tác dụng là gọi tên nó ở đầu prompt: "use the conventions auditor subagent". Một lời gọi duy nhất được gửi đi và một bản tóm tắt ngắn quay về.

Lần chạy Tổng context Dòng Messages
Kiểm tra inline 46.1k 19.3k
Subagent được gọi tên 34.1k 7.2k

Các phát hiện cũng tốt không kém: subagent bắt được một lỗi đường dẫn mà lần chạy inline bỏ sót, và bỏ sót một lỗi mà lần chạy inline tìm thấy. Vậy file làm cho subagent tồn tại, còn prompt của bạn làm cho Claude dùng nó; cho đến khi Claude tự chọn nó, bạn phải gọi tên. Một giới hạn: hóa đơn không đổi. Cả hai lần chạy tốn như nhau, vì việc đọc vẫn xảy ra, chỉ là không nằm trong cửa sổ của bạn.

Việc bốn: dữ liệu Claude không với tới thành MCP server

MCP server là một API client mà Claude có thể gọi, và việc nó sinh ra để làm là dữ liệu mới hơn model hoặc dữ liệu nằm ngoài đĩa của bạn. Lựa chọn theo phản xạ ở đây là sai, nên chúng tôi đã chọn nó trước.

Chúng tôi cắm một server docs công khai cho thư viện test của mình và yêu cầu Claude viết một test với fake timer. Không có gì thay đổi: Claude vốn đã biết thư viện đó, và chỉ gọi server khi chúng tôi nêu tên nó. Docs mà model đã biết không phải là dữ liệu nó không với tới.

Phép thử thật là một câu hỏi về chính Claude Code, về một setting hook mới hơn model: setting nào làm một hook thất bại thì chặn, version nào đã thêm nó, và JSON chính xác là gì. Không có server, Claude đọc schema settings của bản cài đặt của chính nó, không tìm thấy gì, và trả lời rằng nó không tìm thấy setting và sẽ không đoán một cái. Câu trả lời đó thật thà nhưng vô dụng, vì setting đó có tồn tại; nó ra đời sau kiến thức của model.

Cách sửa là thêm server docs của chính Claude Code vào repo, với project scope để cấu hình nằm trong một file ở gốc repo mà cả nhóm của bạn nhận được:

claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp

Lệnh này ghi ra .mcp.json. Claude Code liệt kê server ở trạng thái pending cho đến khi bạn chấp thuận trong phiên kế tiếp, và từ đó các công cụ của nó có sẵn cho Claude. Chúng tôi hỏi lại đúng câu hỏi đó bằng lời thường, không gọi tên server. Claude tìm trong docs, tìm ra trang, và trả lời với setting, version và đoạn JSON cần thêm, kèm đường dẫn.

Setting đó là "onFailure": "block", làm cho một hook thất bại chặn hành động thay vì để nó đi qua. Nó xuất hiện ở v2.1.295, trong khi bản cài đặt của chúng tôi vẫn là 2.1.294. Đó là giới hạn: Claude không bao giờ nói rằng bản cài đặt của chúng tôi không dùng được, vì nó không kiểm tra version, nên bạn phải tự kiểm tra version của mình. Trong bảng context, server chỉ hiện ra bằng tên các công cụ, được nạp theo nhu cầu cho đến khi Claude với tay tới một công cụ.

Bốn file, một thẻ chọn

Sau bốn việc, git status hiện đúng bốn file, vì hook không cần script riêng:

  • .claude/skills/release/SKILL.md
  • .claude/settings.json
  • .claude/agents/conventions-auditor.md
  • .mcp.json

Hãy commit chúng, và phiên kế tiếp trên repo này sẽ có tất cả, cũng như bất kỳ ai clone nó.

Lý do mỗi lựa chọn đều đúng là một bảng mà docs cung cấp, bạn có thể giữ làm thẻ quyết định:

Tình huống Extension
Một quy trình bạn lại dán thêm lần nữa Skill
Thứ phải xảy ra mỗi lần, không cần hỏi Hook
Một việc phụ làm ngập cuộc trò chuyện bằng output bạn sẽ không đọc lại Subagent
Dữ liệu bạn cứ sao chép từ nơi Claude không nhìn thấy MCP server

Các loại này cũng kết hợp được. Docs ghép skill với MCP server: server cung cấp kết nối, còn skill dạy Claude cách dùng nó cho tốt. Một skill cũng 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. Khi một repo khác cần cùng thiết lập, plugin là chiếc hộp đóng gói tất cả chúng lại.

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 từ tấm thẻ, rồi làm cho file trả lời được, chứ không chỉ tồn tại.

Kết luận: khi nào dùng cái nào, khi nào không, một quy tắc

Cả bốn việc bạn từng gõ lại đều bị thử sai cách trước, thất bại trên màn hình, và giờ là các file trong repo hoạt động tốt. Việc phát hành là một lệnh bạn gõ. Quy tắc dữ liệu là một hook từ chối thao tác chỉnh sửa và bắt Claude phải hỏi. Cuộc kiểm tra quay về dưới dạng một bản tóm tắt. Câu hỏi về docs nhận được setting và version mà Claude không tự tìm ra.

Cái hook chúng tôi mở đầu, cái đã chạy và để thao tác ghi lọt qua, dạy quy tắc nằm dưới tất cả những điều này: một hook chỉ quyết định thông qua những gì lệnh của nó trả về, nên một hook chỉ ghi log là một lời đề nghị khoác áo khác.

Một số trường hợp không cần cái nào trong số này:

  • Một quy ước mà Claude cứ làm sai vẫn thuộc về memory file, dưới dạng một dòng, không phải một quy trình.
  • Một việc bạn cầ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 chứ 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ì. Hãy kết nối thứ model không thể có, không phải thứ nó đã có.

Quy tắc cần nhớ: cho mỗi việc bạn gõ lại một file riêng, chọn loại từ tấm thẻ, và biết rằng trong số đó, chỉ hook là không bao giờ hỏi.

Tài nguyên miễn phí

  • Gói video

    Skill, hook, subagent hay MCP: 4 file và thẻ chọn

    Bốn việc bạn gõ lại vào Claude Code, mỗi việc thử cách sai trước, rồi dựng thành file bạn sao chép được, cùng tấm thẻ chọn một trang.

    7 phút đọc · Đừng Gõ Lại Prompt Claude Code: 4 Việc Thành 4 File

Nguồn

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

Tôi nên dùng skill, hook, subagent hay MCP server của Claude Code?
Dùng skill cho quy trình bạn cứ dán, hook cho thứ phải xảy ra mỗi lần mà không cần hỏi, subagent cho một việc phụ làm ngập cuộc trò chuyện, và MCP server cho dữ liệu Claude không nhìn thấy. Docs cung cấp điều này dưới dạng một bảng quyết định.
Tại sao hook Claude Code của tôi chạy nhưng không chặn thao tác chỉnh sửa?
Một hook chỉ quyết định thông qua những gì lệnh của nó trả về. Một hook in cảnh báo rồi thoát với mã 0 là chưa đưa ra quyết định nào, nên thao tác chỉnh sửa vẫn lọt qua; trả về permissionDecision là deny, hoặc thoát với mã 2, thì sẽ chặn.
Tôi nên đặt checklist phát hành trong CLAUDE.md hay trong một skill?
Trong một skill. Mọi thứ trong memory file được nạp ở mỗi lượt của mỗi phiên (210 token cho checklist sáu bước của chúng tôi), trong khi một skill có disable-model-invocation không tốn gì trong context cho đến khi bạn gõ tên nó.
Tại sao Claude Code không tự gọi subagent của tôi?
Dù mô tả khớp với prompt của chúng tôi gần như từng chữ, Claude vẫn tự đọc các file trong 2 trên 2 lần chạy. Gọi tên subagent ở đầu prompt khiến nó ủy quyền, và dòng Messages giảm từ 19.3k xuống 7.2k token.
Khi nào một MCP server đáng được thêm vào Claude Code?
Khi dữ liệu mới hơn model hoặc nằm ngoài đĩa của bạn. Một server docs cho thư viện model đã biết không thay đổi gì, trong khi server docs của Claude Code trả lời được câu hỏi về một setting hook mà schema của bản cài đặt không có.
Làm sao thêm MCP server docs của Claude Code vào một repo?
Chạy claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp. Lệnh này ghi .mcp.json ở gốc repo, và server hiện ở trạng thái pending cho đến khi bạn chấp thuận trong phiên kế tiếp.

Video liên quan