Một giờ làm việc bình thường với chế độ Manual: agent đọc dự án, đề xuất chỉnh sửa, yêu cầu chạy test, rồi lại chỉnh sửa, lại test. Đến câu hỏi thứ hai mươi, bạn gần như chẳng nhìn mà đã nhấn "Yes". Và đâu đó giữa những câu hỏi ấy, một chỉnh sửa trong thư mục legacy/ lọt qua, nơi từ lâu không ai đụng tới và cũng không nên đụng vào.
Bài viết này hướng dẫn cách chỉ giữ lại những câu hỏi mà quyết định thực sự thuộc về bạn. Claude Code có quy tắc quyền cho việc đó: ba danh sách trong tệp cài đặt, chỉ rõ agent được làm gì ngay, cần hỏi về việc gì và tuyệt đối không được làm gì. Ta sẽ xem vì sao cần các quy tắc này, cách đọc chúng và cách dọn lại tệp quy tắc có lẽ đã tồn tại trong dự án của bạn.
Một chế độ cho mọi việc
Chế độ quyền giống như một núm chỉnh cho cả phiên. Bạn có thể vặn theo hướng nào cũng được, nhưng chỉ áp dụng cho toàn bộ.
Ở chế độ nghiêm ngặt Manual, agent hỏi trước mỗi lần chỉnh sửa và gần như trước mỗi lệnh. Câu hỏi chỉ bảo vệ bạn khi bạn còn đọc chúng. Khi câu hỏi quá nhiều, người ta thôi đọc, và sự bảo vệ chỉ còn trên lý thuyết.
Ở chế độ thoáng hơn acceptEdits, các chỉnh sửa được áp dụng mà không cần hỏi, và cả những lệnh thao tác tệp như rm và mv cũng vậy. Với mã của bạn thì tiện. Nhưng chế độ không phân biệt thư mục: thay đổi trong legacy/ cũng lặng lẽ được áp dụng như ở bất kỳ nơi nào khác trong dự án.
Bạn có thể ghi vào CLAUDE.md rằng "đừng đụng vào legacy/". Agent sẽ cố làm theo, nhưng đó là lời đề nghị gửi mô hình chứ không phải lệnh cấm. Claude Code tự kiểm tra quy tắc quyền trước khi thực hiện hành động; văn bản trong CLAUDE.md hay yêu cầu của bạn không ảnh hưởng đến việc kiểm tra này.
Vậy là mỗi hành động có cái giá của sai sót khác nhau, nhưng chế độ lại xử lý tất cả như nhau. Chạy test thì có thể cho phép âm thầm. Chỉnh sửa mô-đun của người khác thì bạn muốn xem trước. Còn agent tuyệt đối không nên vào thư mục đã đóng băng. Ba hành động cần ba phản hồi khác nhau, nhưng chế độ không làm được điều đó.
Ba danh sách
Quy tắc chính là cách đưa ra ba phản hồi đó:
allow- thực hiện mà không hỏi. Bớt nhiễu: việc thường xuyên và an toàn không còn làm bạn mất tập trung;ask- luôn hỏi. Giữ câu hỏi ở nơi quyết định thuộc về bạn, bất kể đang dùng chế độ nào, kể cả chế độ thoáng nhất;deny- không thực hiện. Agent bị từ chối và bạn thậm chí không thấy câu hỏi. Quy tắc này cũng áp dụng ở mọi chế độ.
Các quy tắc được ghi vào tệp cài đặt, chẳng hạn .claude/settings.json của dự án. Với dự án ở phần mở đầu, tệp có thể trông như sau:
{
"permissions": {
"allow": [
"Bash(npm test)"
],
"ask": [
"Edit(src/partner/**)"
],
"deny": [
"Edit(legacy/**)",
"Bash(git push *)"
]
}
}Ở đây, src/partner/ là mô-đun của người khác nên bạn chỉnh sửa thận trọng; legacy/ là phần đã đóng băng của dự án; còn npm test là bài kiểm tra agent chạy cả chục lần trong một nhiệm vụ. Hai dấu sao có nghĩa là "mọi thứ bên trong thư mục, ở bất kỳ cấp nào".
Giờ hãy xem phiên làm việc thay đổi ra sao. Cùng năm hành động, qua ba tình huống:
| Hành động | Manual, không có quy tắc | acceptEdits, không có quy tắc | acceptEdits, có quy tắc |
|---|---|---|---|
Sửa ở phần còn lại của src/ | hỏi | không hỏi | không hỏi |
Sửa trong src/partner/ | hỏi | không hỏi | hỏi |
Sửa trong legacy/ | hỏi | không hỏi | từ chối |
npm test | hỏi | hỏi | không hỏi |
git push | hỏi | hỏi | từ chối |
Hãy nhìn cột cuối cùng. Phiên chạy ở chế độ nhanh: mã của bạn được sửa liên tục, test tự chạy. Chỉ còn một câu hỏi, về đúng chỗ bạn muốn xem lại thay đổi. Hai hành động không được phép cũng không còn phụ thuộc vào việc cuối ngày bạn mệt đến mức nào.
Đó là lý do cần có quy tắc. Mục tiêu không phải là cấp thêm quyền cho agent, mà là phân loại quyền theo cái giá của sai sót. Nhờ vậy, bạn có thể làm việc ở chế độ thoáng hơn mà vẫn kiểm soát được những phần quan trọng.
Một hành động có thể khớp với nhiều quy tắc, vì vậy Claude Code kiểm tra các danh sách theo thứ tự cố định:
Sơ đồ được đọc từ trên xuống, và mỗi hành động của agent đều được kiểm tra lại từ đầu. Khi một quy tắc khớp, quá trình kiểm tra kết thúc; quy tắc cụ thể hơn không được ưu tiên hơn. Nếu deny có Bash(git push *), thì không quy tắc nào trong allow có thể cho phép push. Không thể vô tình ghi đè lệnh cấm, và như vậy là tốt.
Hãy nhớ riêng hình chữ nhật dưới cùng. Nếu hành động không khớp danh sách nào, điều đó không có nghĩa là "được phép". Chế độ sẽ quyết định: ở Manual, agent hỏi; ở acceptEdits, chỉnh sửa được áp dụng luôn. Nói cách khác, quy tắc không thay thế chế độ, chúng tinh chỉnh chế độ ở những chỗ bạn đã chỉ định.
Cách đọc một quy tắc
Một quy tắc viết gần đúng còn tệ hơn là không có quy tắc. Khi không có quy tắc, bạn biết ranh giới chưa được thiết lập. Còn với quy tắc sai lệch, bạn tưởng đã có ranh giới, nhưng nó lại bảo vệ một thứ khác với điều bạn nghĩ.
Mọi quy tắc đều có cùng định dạng: tên công cụ, rồi phần chỉ rõ nội dung trong ngoặc. Không có ngoặc thì quy tắc áp dụng cho mọi lần gọi công cụ, nên Bash trong deny sẽ khóa toàn bộ terminal của agent.
Tệp
Với tệp, cần hai công cụ: Edit cho mọi chỉnh sửa và Read để đọc. Đường dẫn được ghi theo mẫu như trong .gitignore: một dấu sao khớp mọi tên trong một thư mục, hai dấu sao khớp mọi cấp thư mục lồng nhau. Đường dẫn không bắt đầu bằng dấu gạch chéo được tính từ thư mục nơi phiên khởi chạy, vì vậy hãy chạy Claude Code từ thư mục gốc của dự án.
Một lỗi thường gặp là ghi Write(legacy/**). Agent có công cụ mang tên này, nhưng Claude Code chỉ đối chiếu quy tắc tệp với Edit và Read. Quy tắc Write vẫn được chấp nhận, nhưng Claude Code sẽ cảnh báo lúc khởi chạy rồi không kiểm tra quy tắc đó nữa. Còn Edit bao quát mọi cách tích hợp sẵn để thay đổi tệp, kể cả tạo tệp mới.
Một điểm hữu ích nữa: lệnh cấm đọc, chẳng hạn Read(./.env) trong deny, đồng thời cũng cấm agent chỉnh sửa tệp này.
Lệnh
Quy tắc Bash(...) so khớp với nội dung lệnh. Dấu sao thay thế mọi nội dung, kể cả dấu cách; có ba chi tiết quyết định quy tắc có khớp hay không:
- không có dấu sao thì quy tắc khớp chính xác với một lệnh, từng ký tự một;
- đặt dấu sao sau lệnh con.
Bash(git log *)chỉ cho phépgit logvới mọi cờ, cònBash(git *)cho phép mọi lệnh git, kể cả push; - dấu cách trước dấu sao cũng là một phần của quy tắc:
Bash(ls *)không khớp vớilsof, cònBash(ls*)thì có.
Phần màu xanh dương được so khớp từng ký tự với lệnh; dấu sao bao phủ toàn bộ phần màu cam, bất kể nội dung là gì. Vì vậy, phạm vi quy tắc phụ thuộc vào phần đứng trước dấu sao. Ở quy tắc đầu tiên, đó là git log, nên chỉ các lệnh xem lịch sử mới được phép. Ở quy tắc thứ hai, chỉ có git, và sau dấu sao có thể là push, xóa nhánh hoặc đặt lại thay đổi. Khi đọc quy tắc của người khác, hãy bắt đầu bằng câu hỏi này: phần nào đứng trước dấu sao?
| Quy tắc | Khớp | Không khớp |
|---|---|---|
Bash(npm test) | npm test | npm test --watch |
Bash(npm run *) | npm run build, npm run lint --fix | npm install |
Bash(git log *) | git log, git log --oneline | git push |
Bash(git *) | mọi lệnh git, kể cả git push | - |
Edit(src/partner/**) | chỉnh sửa mọi tệp trong src/partner/ | chỉnh sửa src/app.js |
Read(./.env) | đọc .env ở thư mục gốc dự án | đọc .env.example |
Claude Code chia lệnh ghép như a && b thành từng phần. allow chỉ cho phép chạy nếu từng phần đều khớp với quyền tương ứng. ask và deny có hiệu lực nếu ít nhất một phần khớp. Vì vậy, cho phép npm test không đồng nghĩa với việc cho phép cả npm test && git push.
Cũng có quy tắc cho các công cụ khác, chẳng hạn mcp__github__* áp dụng cho mọi công cụ của một MCP server hoặc WebFetch(domain:example.com) để tải trang từ một website. Chúng hoạt động theo cùng logic, nhưng ta sẽ không phân tích riêng ở đây.
Nên đặt quy tắc trong tệp nào
Đặt quy tắc sai tệp có thể âm thầm làm hỏng quy trình. Nếu bạn đặt lệnh cấm trong tệp cá nhân, đồng nghiệp sẽ không có quy tắc đó và agent của họ vẫn vào legacy/ như không có chuyện gì. Còn nếu đặt quyền tiện cho riêng bạn vào tệp chung, nó sẽ được chia sẻ với tất cả những ai tải dự án về.
| Tệp | Quy tắc của ai | Có trong Git |
|---|---|---|
~/.claude/settings.json | của bạn, áp dụng cho mọi dự án trên máy tính này | không, nằm trong thư mục home |
.claude/settings.json | dùng chung cho mọi người làm việc với dự án | có, được commit |
.claude/settings.local.json | của bạn, chỉ trong dự án này | không |
Ngoài ba tệp này, tổ chức có thể cung cấp tệp cài đặt riêng và bạn không thể ghi đè quy tắc trong đó. Các dự án học tập thường không có tệp như vậy.
Các danh sách trong những tệp khác nhau không xung đột mà được gộp lại: allow trong tệp của bạn được thêm vào allow chung. Thứ tự kiểm tra giống nhau ở mọi nơi; deny trong bất kỳ tệp nào cũng được ưu tiên hơn allow trong các tệp khác. Vì vậy, hãy phân chia đơn giản: đặt ranh giới của dự án như legacy/, .env hay push vào tệp chung để chúng đi cùng repository. Còn các tiện ích theo cách làm việc riêng của bạn thì đặt trong tệp cục bộ.
Tệp chung có một điểm bảo vệ. Quy tắc allow trong đó chỉ bắt đầu có hiệu lực sau khi bạn xác nhận tin cậy thư mục: lần đầu chạy Claude Code trong dự án, ứng dụng sẽ hỏi và cho bạn biết tệp này định cho phép những gì. Quy tắc ask và deny có hiệu lực ngay vì chúng chỉ hạn chế. Một repository lạ không thể âm thầm có quyền dùng toàn bộ terminal trước khi bạn đồng ý.
Bạn có thể kiểm tra những gì thực sự đang có hiệu lực bằng hai lệnh. /permissions hiển thị mọi quy tắc cùng tệp chứa quy tắc đó; bạn cũng có thể thêm hoặc xóa quy tắc ngay tại đây. Dòng Setting sources trong /status liệt kê các tệp đã được tải. Claude Code nhận thay đổi trong tệp mà không cần khởi động lại. Tệp cài đặt dùng JSON nghiêm ngặt: thêm chú thích bằng // hoặc dấu phẩy sau phần tử cuối cùng sẽ làm JSON không hợp lệ, và Claude Code sẽ cảnh báo khi khởi động.
Khi agent hỏi về một lệnh và bạn chọn "Yes, and don't ask again", Claude Code ghi quy tắc allow vào .claude/settings.local.json. Nếu tệp chưa tồn tại, Claude Code sẽ tạo tệp và đảm bảo nó không bị đưa vào commit. Với chỉnh sửa tệp thì khác: lựa chọn đồng ý chỉ có hiệu lực đến hết phiên và không được ghi vào tệp.
Phân tích một settings.local.json thực tế
Có lẽ tệp này đã tồn tại trong dự án của bạn, và không phải do bạn tự viết. Mỗi lần chọn "đừng hỏi lại" lại thêm một dòng; sau vài tuần, tệp có thể cấp cho agent nhiều quyền hơn bạn nghĩ. Đây là một phần cấu hình thực tế tích lũy sau vài tuần làm việc với dự án Python:
{
"permissions": {
"allow": [
"Bash(python3 *)",
"Bash(PYTHONIOENCODING=utf-8 python3 *)",
"Bash(git status *)",
"Bash(git log *)",
"Bash(git diff *)",
"Bash(git show *)",
"Bash(git branch *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git fetch *)"
]
}
}Trước khi đọc phần phân tích, hãy thử tự xem trước. Bạn sẽ giữ lại dòng nào, thu hẹp dòng nào và xóa dòng nào?
Bash(python3 *)
Đây là quy tắc rộng nhất trong tệp. Nó cho phép chạy mọi script và mọi đoạn mã thông qua python3 -c. Python có thể làm mọi thứ: xóa tệp, truy cập mạng, chạy chương trình khác. Nói cách khác, quy tắc này cho phép thực hiện bất kỳ việc gì mà không cần hỏi. Nên thu hẹp phạm vi xuống script cụ thể mà quy tắc này vốn được tạo ra để chạy, chẳng hạn Bash(python3 scripts/build.py *).
Bash(PYTHONIOENCODING=utf-8 python3 *)
Cùng lệnh đó nhưng có thêm biến môi trường ở phía trước. Quy tắc allow chỉ cho qua cách ghi này nếu biến nằm trong danh sách ngắn các biến đã biết là an toàn. Nếu không có trong danh sách, Bash(python3 *) sẽ không khớp, agent hỏi riêng và nút xác nhận sẽ lưu thêm một quy tắc. Có lẽ dòng này được tạo ra theo cách đó. Hãy thu hẹp nó tương tự, xuống đúng script cụ thể.
git status, git log, git diff, git show
Ở dạng thông thường, các lệnh này chỉ đọc. Claude Code chạy lệnh chỉ đọc mà không cần hỏi và không cần quy tắc nào. Những dòng này không gây hại nhưng gần như chẳng thêm ích lợi gì. Xóa chúng đi, tệp sẽ ngắn và dễ hiểu hơn.
Bash(git branch *)
Trông có vẻ vô hại: xem danh sách nhánh. Nhưng quy tắc so khớp nội dung, và git branch -D feature cũng bắt đầu bằng git branch. Lệnh xóa nhánh sẽ chạy mà không hỏi. Tốt hơn là xóa dòng này. Nếu câu hỏi khi xem danh sách nhánh bắt đầu gây phiền, hãy cho phép chính xác lệnh Bash(git branch), không có dấu sao.
git add và git fetch
Lệnh đầu thêm thay đổi vào commit sắp tới; lệnh sau tải lịch sử mới từ máy chủ và không đụng đến các tệp trong thư mục làm việc. Cả hai đều dễ hoàn tác, có thể giữ lại.
Bash(git commit *)
Agent commit mà không hỏi, tức là trước khi bạn đọc diff. Đây chính là nơi cần câu hỏi: commit là thời điểm bạn đồng ý với thay đổi. Nên chuyển dòng này sang ask. Chỉ xóa nó thôi thì chưa đủ. Ở Manual, agent vốn đã hỏi, nhưng ask giữ câu hỏi ở mọi chế độ, kể cả chế độ thoáng nhất.
Điều không có trong tệp
git push. Điều này tốt, nhưng chỉ vì chưa ai từng nhấn "đừng hỏi lại" khi push. Đáng tin cậy hơn là ghi rõ lệnh cấm.
Sau khi phân tích, cấu hình sẽ như sau:
{
"permissions": {
"allow": [
"Bash(python3 scripts/build.py *)",
"Bash(PYTHONIOENCODING=utf-8 python3 scripts/build.py *)",
"Bash(git add *)",
"Bash(git fetch *)"
],
"ask": [
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)"
]
}
}
Trong mười dòng ban đầu, chỉ còn sáu, và dòng nào cũng có mục đích rõ ràng. Số câu hỏi hầu như không tăng: lệnh build tự chạy, agent chỉ hỏi một lần trước khi commit. Nếu lệnh cấm push áp dụng cho cả nhóm, tốt hơn nên chuyển nó sang .claude/settings.json dùng chung, theo nguyên tắc ở phần trước.
Các quy tắc như Bash(python3 *), Bash(node *), Bash(npm *) hoặc Bash(npx *) trông như chỉ cấp quyền cho một công cụ. Thực tế, chúng cho phép chạy mọi đoạn mã mà công cụ đó có thể thực thi. Hãy cho phép một script hoặc lệnh cụ thể, thay vì cấp quyền cho cả chương trình.
Những gì quy tắc không bắt được
Agent bị từ chối. Với nó, đây chỉ là phản hồi thông thường từ công cụ, và công việc vẫn tiếp tục. Đôi khi agent tìm cách khác để làm cùng một việc; khi đó bạn cần hiểu chính xác quy tắc kiểm tra điều gì.
Quy tắc Bash(...) so khớp nội dung lệnh chứ không kiểm tra chương trình sẽ chạy. Vì vậy, lệnh cấm ngăn được cách viết thông thường của lệnh chứ không phải mọi cách viết:
Bash(git push *)trongdenykhông chặn đượcgit -C . push. Lệnh giống nhau, cách viết khác;Bash(rm *)không chặn được/bin/rmhaybash -c 'rm ...';- lệnh cấm
Read(...)không ngăn được một script Python tự mở tệp.
Không cần vì thế mà bỏ các quy tắc. Agent gần như luôn ghi lệnh theo cách thông thường và deny sẽ bắt đúng cách viết đó. Nhưng quy tắc không phải bức tường kín (tài liệu Claude Code cũng nói rõ điều này). Để agent không phải tìm đường vòng, hãy giải thích lệnh cấm bằng lời trong CLAUDE.md: "legacy/ đã đóng băng; nếu nhiệm vụ yêu cầu chỉnh sửa ở đó, hãy dừng lại và hỏi". Quy tắc giữ ranh giới, còn CLAUDE.md giải thích lý do; kết hợp lại sẽ hiệu quả hơn dùng riêng từng thứ.
Nếu cần đảm bảo ranh giới được giữ chặt, có các cơ chế khác: hook kiểm tra từng lệnh trước khi chạy và sandbox giới hạn quyền truy cập ở cấp hệ thống. Đó là những chủ đề riêng.
Quy tắc dùng hằng ngày
Có thể gói gọn mọi thứ thành vài thói quen:
- đưa vào
denynhững gì tuyệt đối không được phép trong dự án này: thư mục đã đóng băng,.env, push; - chỉ đưa vào
asknhững việc ở ranh giới mà quyết định thuộc về bạn. Đặtaskcho mọi thứ sẽ khiến bạn lại mệt mỏi vì quá nhiều câu hỏi; - đưa vào
allownhững quyền hẹp và lặp lại: một bài kiểm tra cụ thể, một script cụ thể; - đặt ranh giới của dự án trong
.claude/settings.jsondùng chung, còn tiện ích cá nhân trong.claude/settings.local.json; - thỉnh thoảng mở
/permissionsvà xóa những gì đã tích lũy từ nút "đừng hỏi lại"; - sau khi thêm quy tắc, hãy kiểm tra nó: yêu cầu agent làm việc bị cấm và xác nhận rằng agent nhận được thông báo từ chối.
Hãy mô tả bằng lời các khu vực trong dự án: nơi nào có thể tự do chỉnh sửa, nơi nào cần hỏi và nơi nào không được vào. Yêu cầu agent đề xuất khối permissions, rồi đọc JSON trước khi lưu và tự hỏi như trong phần phân tích ở trên: quy tắc có rộng hơn mức cần thiết không? Nó có nằm đúng tệp không?
Chế độ quyết định nhịp độ chung, còn quy tắc xác định ở đâu cần giảm nhịp. Kết hợp lại, chúng làm được điều mà riêng từng chế độ không thể: giúp bạn làm nhanh ở nơi sai sót không quá tốn kém, và hỏi hoặc từ chối ở nơi cái giá phải trả cao hơn.
GO TO FULL VERSION