PermissionRequest hook của Claude Code chạy đúng lúc Claude Code sắp hiển thị prompt xin quyền, và có thể trả lời prompt đó thay bạn. Để cho phép, hook thoát với 0 và in ra {"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": {"behavior": "allow"}}}; để từ chối, dùng "behavior": "deny" kèm một message cho Claude. Hook match theo tên tool, không thể ghi đè các deny rule của bạn, và exit code 2 không có tác dụng với event này. Nếu hook không in ra quyết định nào, prompt bình thường sẽ hiện ra.
01So sánh PermissionRequest với PreToolUse và Notification
Có ba hook event liên quan đến quyền, và mỗi cái làm một việc khác nhau:
PreToolUseTrước mọi lần gọi tool, dù có cần quyền hay khôngCó: allow, deny, ask hoặc deferPermissionRequestChỉ khi Claude Code sắp hỏi bạnCó: allow hoặc denyNotification (permission_prompt)Sau khi prompt đã chờ khoảng sáu giây trong lúc bạn vắng mặtKhôngDùng PermissionRequest khi bạn quan tâm đến chính các prompt: trả lời, ghi log hoặc gửi chúng đi đâu đó. Nó chạy ngay lập tức, không có độ trễ sáu giây. Dùng PreToolUse cho các rule cần áp dụng với mọi lần gọi, chẳng hạn "không bao giờ chạy rm -rf", vì nó chạy cả khi không có prompt nào xuất hiện. Hướng dẫn Notification hook giải thích thời điểm chạy của event đó.
02Hook nhận được gì
Ngoài các trường chung, PermissionRequest hook nhận tool_name và tool_input của lệnh gọi mà Claude muốn thực hiện, cùng một mảng permission_suggestions tùy chọn:
{
"session_id": "abc123",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
}
permission_suggestions chứa các thay đổi rule mà Claude Code sẽ gợi ý cho yêu cầu này. Nó không phải bản sao chính xác các lựa chọn trong hộp thoại, vì mỗi hộp thoại tự tạo lựa chọn riêng.
03Object decision
Trả về một decision bên trong hookSpecificOutput:
behavior"allow" hoặc "deny"updatedInputChỉ với allow. Thay thế toàn bộ input của tool, nên hãy đưa cả các trường không đổi vàoupdatedPermissionsChỉ với allow. Các thay đổi quyền cần áp dụng, như thêm rule hoặc đổi modemessageChỉ với deny. Cho Claude biết lý dointerruptChỉ với deny. true sẽ dừng luôn ClaudeHai quy tắc cần nhớ:
- Các rule deny và ask vẫn được áp dụng. Hook trả về
"allow"không ghi đè được rule deny khớp trong settings của bạn, và input đã sửa sẽ được kiểm tra lại với các rule đó. - Exit code
2bị bỏ qua ở đây. Với hầu hết event, exit2sẽ chặn, nhưng hookPermissionRequestexit2mà không có objectdecisionthì prompt vẫn giữ nguyên. Muốn từ chối, hãy trả về decision.
04Ví dụ: tự động chấp thuận một prompt
Ví dụ của chính Anthropic chấp thuận ExitPlanMode, prompt xuất hiện khi Claude lập xong kế hoạch, để bạn không bị hỏi mỗi lần:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
Hãy giữ matcher thật hẹp. Một matcher rỗng hoặc .* kèm allow toàn bộ sẽ chấp thuận mọi prompt, kể cả ghi file và lệnh shell.
05Ví dụ: quyết định dựa trên lệnh
Script này (cần jq) từ chối mọi thứ liên quan đến git push kèm lý do mà Claude đọc được, và để mọi prompt khác cho bạn bằng cách không in gì:
#!/bin/bash
# ~/.claude/hooks/permission-check.sh
input=$(cat)
cmd=$(jq -r '.tool_input.command // empty' <<<"$input")
case "$cmd" in
*"git push"*)
jq -nc '{hookSpecificOutput: {hookEventName: "PermissionRequest",
decision: {behavior: "deny",
message: "Pushing is not allowed from this session. Commit and stop."}}}'
;;
esac
exit 0
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/permission-check.sh" }]
}
]
}
}
Hãy tạo JSON bằng jq thay vì nối chuỗi, để dấu nháy trong message không làm hỏng nó. Đồng thời đảm bảo không có gì khác in ra stdout. Nếu shell profile của bạn echo một dòng nào đó, Claude Code sẽ không đọc được decision.
06Ví dụ: cho phép và ghi nhớ trong session
Để chấp thuận một request và không để Claude hỏi lại cho đến hết session, hãy thêm một mục updatedPermissions với destination: "session". Hook có thể echo lại một trong các permission_suggestions nhận được, chỉ đổi destination:
#!/bin/bash
input=$(cat)
jq -c '{hookSpecificOutput: {hookEventName: "PermissionRequest",
decision: {behavior: "allow",
updatedPermissions: [(.permission_suggestions // [])[]
| select(.type == "addRules")
| .destination = "session"]}}}' <<<"$input"
Như đang viết, script này cho phép mọi request nó thấy, nên hãy dùng kèm một matcher hẹp. Destination cũng có thể là localSettings, projectSettings hoặc userSettings, khi đó rule được ghi vào file settings thay vì chỉ giữ trong bộ nhớ.
07Thường thì permission rule là lựa chọn tốt hơn
Nếu câu trả lời không bao giờ thay đổi, bạn không cần hook. Hãy đặt rule trong settings.json và Claude Code sẽ áp dụng mà không cần chạy gì:
{
"permissions": {
"allow": ["Bash(npm run *)"],
"deny": ["Bash(git push *)"]
}
}
Rule được đánh giá theo thứ tự deny, rồi ask, rồi allow. Hook chỉ thật sự cần khi quyết định phụ thuộc vào thứ mà rule không thấy được, như thời điểm trong ngày, branch bạn đang ở, hay một người trả lời từ nơi khác. Giải thích các permission mode trình bày các tùy chọn có sẵn khác.
08Những điểm dễ vấp
- Chạy non-interactive. Với
claude -p, không có ai để hỏi. HookPermissionRequestvẫn chạy, và nếu không hook nào trả về decision thì lệnh gọi bị từ chối. - Timeout. Mặc định một command hook có thể chạy tới 600 giây, và Claude sẽ chờ. Hãy đặt
timeout(tính bằng giây) nếu hook của bạn phải chờ thứ gì đó chậm, chẳng hạn một người. - Một số prompt không kích hoạt nó. Network request của lệnh chạy trong sandbox không chạy
PermissionRequest. Hãy dùng loại notificationpermission_promptcho trường hợp đó. - Bảo mật. Hook trả về
"allow"đang chấp thuận hành động thay bạn với quyền của user của bạn. Hãy review nó như mọi đoạn code khác có thể chạy lệnh.
09Claude Code Notifier nằm ở đâu trong bức tranh này
Lý do phổ biến nhất để viết hook PermissionRequest là trả lời prompt khi bạn không ngồi trước bàn phím. Bản tự làm cần hook gửi request đi đâu đó, chờ câu trả lời và trả về trước khi hết timeout. Việc này làm được, và có người đã xây nó bằng chat bot.
Claude Code Notifier làm sẵn việc này cho bạn. Công cụ dòng lệnh miễn phí của nó, ccn, chạy trên Mac, Windows và Linux, và cài hook vào ~/.claude/settings.json. Khi đã ghép nối app iPhone hoặc Android, permission request sẽ đến điện thoại và bạn có thể cho phép một lần, cho phép trong session, hoặc từ chối; ccn trả quyết định đó về từ hook PermissionRequest. Nếu bạn vừa gõ phím trong 30 giây gần nhất, không có gì phải chờ và terminal hỏi như bình thường. Nếu bạn vắng mặt, terminal chờ điện thoại tối đa đến mức bạn chọn (hold_ceiling_seconds, mặc định 600), và ngừng chờ ngay khi bạn chạm vào bàn phím. Đây là sản phẩm độc lập, không phải do Anthropic làm. Xem chấp thuận permission từ điện thoại để biết quy trình và chấp thuận từ xa có an toàn không? về khía cạnh bảo mật.
10Câu hỏi thường gặp
Làm sao để tự động chấp thuận permission bằng hook?
Thêm một hook PermissionRequest với matcher hẹp, cho nó in ra JSON decision có "behavior": "allow" bên trong hookSpecificOutput, rồi exit 0. Nếu cùng một lệnh luôn được cho phép, một rule permissions.allow trong settings.json đơn giản hơn và không cần hook.
Tại sao exit code 2 không từ chối request?
PermissionRequest là một trong những event không tôn trọng exit 2. Claude Code bỏ qua nó và hiển thị prompt bình thường. Thay vào đó, hãy trả về "behavior": "deny" trong object decision.
Hook PermissionRequest có ghi đè được rule deny của tôi không?
Không. Các rule deny và ask vẫn được đánh giá, nên "allow" từ hook không vượt qua được rule deny khớp. Mọi updatedInput bạn trả về cũng được kiểm tra với các rule đó.
PermissionRequest và PreToolUse khác nhau thế nào?
PreToolUse chạy trước mọi tool call và có thể allow, deny, ask hoặc defer. PermissionRequest chỉ chạy khi Claude Code sắp hiển thị prompt, và có thể allow hoặc deny prompt đó. Dùng PreToolUse cho policy, PermissionRequest để trả lời prompt.
Điều gì xảy ra nếu hook của tôi không in ra gì?
Không có gì thay đổi. Claude Code hiển thị permission prompt như thường trong terminal, như thể không có hook.