跳到主要内容

如何自定义权限规则:hooks.json 配置详解

🟢 入门

Claude Code 的 Hook 系统允许你在工具执行的关键节点插入自定义逻辑。通过编写 Hook 配置,不需要修改 Claude Code 源码,就能实现:记录审计日志、阻止危险操作、发送通知提醒、集成外部系统等功能。

配置文件位置

Hooks 配置不是独立的 hooks.json 文件,而是集成在 Claude Code 统一的 settings.json 配置文件中,通过 hooks 字段定义。

配置文件有三个层级:

1. 用户级(全局)

~/.claude/settings.json

对当前用户的所有项目生效。适合配置个人习惯性的安全规则。

2. 项目级

<project-root>/.claude/settings.json

只对当前项目生效,可以提交到版本控制,在团队间共享。适合项目特定的工作流 Hook。

3. 项目本地级(不提交)

<project-root>/.claude/settings.local.json

只对当前项目生效,但不应提交到版本控制(.gitignore 中排除)。适合个人开发习惯配置。

4. 插件 Hooks

~/.claude/plugins/*/hooks/hooks.json

由 Claude Code 插件提供的 Hook,只读(不建议手动修改)。

优先级顺序

当多个层级都配置了相同事件的 Hook 时,所有层级的 Hooks 都会执行(不是覆盖,而是合并)。执行顺序:用户级 → 项目级 → 本地级 → 插件级。

配置格式详解

settings.jsonhooks 字段的完整结构:

{
"hooks": {
"<事件名>": [
{
"matcher": "<匹配规则(可选)>",
"hooks": [
{
"type": "command",
"command": "<shell 命令>",
"timeout": 30,
"if": "<精细化过滤条件(可选)>"
}
]
}
]
}
}

matcher 字段

matcher 指定哪些工具触发这条规则。对于 PreToolUsePostToolUse,matcher 匹配的是工具名(tool_name):

"matcher": "Bash"       // 只匹配 Bash 工具
"matcher": "Write" // 只匹配 Write 工具
"matcher": "" // 或不填,匹配所有工具

if 字段(精细化过滤)

if 字段支持权限规则语法(Permission Rule Syntax),在 matcher 基础上进一步精确过滤:

{
"type": "command",
"command": "...",
"if": "Bash(git *)" // 只对 git 相关命令触发
}

这个过滤在 Hook 被调用之前就完成,可以减少不必要的子进程启动。

支持的 Hook 类型

类型说明
command执行 shell 命令(最常用)
prompt调用 LLM 做判断
http发送 HTTP 请求
agent启动一个 Agent 做复杂验证

实际示例 1:记录所有工具调用到日志文件

需求: 将每次工具调用(工具名 + 关键参数)记录到审计日志文件。

配置(~/.claude/settings.json):

{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'echo \"[$(date +%Y-%m-%dT%H:%M:%S)] $(cat)\" >> ~/.claude/audit.log'",
"async": true
}
]
}
]
}
}

async: true 让日志记录在后台进行,不阻塞工具执行。

效果: ~/.claude/audit.log 中会记录如下内容:

[2026-04-01T10:30:45] {"session_id":"abc123","tool_name":"Bash","tool_input":{"command":"ls -la"}}
[2026-04-01T10:30:47] {"session_id":"abc123","tool_name":"Write","tool_input":{"file_path":"README.md","content":"..."}}

更精细的日志脚本(~/.claude/hooks/audit-logger.sh):

#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name','?'))")
TIMESTAMP=$(date +"%Y-%m-%dT%H:%M:%S")

# 只记录写操作
case "$TOOL" in
Write|Edit|MultiEdit|Bash)
echo "[$TIMESTAMP] $TOOL: $(echo "$INPUT" | python3 -c "
import sys,json
d=json.load(sys.stdin)
inp=d.get('tool_input',{})
if 'command' in inp:
print(inp['command'][:100])
elif 'file_path' in inp:
print(inp['file_path'])
else:
print('...')
")" >> ~/.claude/audit.log
;;
esac
exit 0

配置:

{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/audit-logger.sh",
"async": true
}
]
}
]
}
}

实际示例 2:禁止删除某个重要目录

需求: 阻止 Claude 删除 ~/important-data/ 目录中的任何文件。

Hook 脚本(~/.claude/hooks/protect-important.sh):

#!/bin/bash
INPUT=$(cat)

# 提取工具名和命令
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))")
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path',''))")

PROTECTED_DIR="$HOME/important-data"

# 检查 Bash 命令是否涉及受保护目录
if [ "$TOOL" = "Bash" ]; then
if echo "$COMMAND" | grep -q "$PROTECTED_DIR"; then
if echo "$COMMAND" | grep -qE '(rm|rmdir|mv|chmod|chown|unlink)'; then
echo "禁止操作:不允许在 $PROTECTED_DIR 中执行删除或移动操作" >&2
echo "如需修改此目录,请先手动备份,然后联系管理员" >&2
exit 2 # 拒绝执行,并将消息发给 Claude
fi
fi
fi

# 检查文件写入操作是否涉及受保护目录(理论上 Write 不能删,但可以加检查)
if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ]; then
case "$FILE_PATH" in
"$PROTECTED_DIR"/*)
echo "警告:正在尝试修改受保护目录中的文件:$FILE_PATH" >&2
echo "此操作已被拦截" >&2
exit 2
;;
esac
fi

exit 0

配置:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/protect-important.sh",
"timeout": 5
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/protect-important.sh",
"timeout": 5
}
]
}
]
}
}

当 Claude 尝试执行 rm ~/important-data/backup.tar.gz 时,Hook 返回 exit code 2,Claude 会收到错误消息并停止操作。

实际示例 3:在执行 bash 命令前发送通知

需求: 当 Claude 要执行 bash 命令时,通过系统通知提醒用户(macOS)。

配置(直接在 settings.json 内联命令):

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 正在执行 Bash 命令\" with title \"Claude Code\" sound name \"Ping\"'",
"async": true,
"timeout": 3
}
]
}
]
}
}

Linux 用户可以使用 notify-send

{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude 正在执行 Bash 命令'",
"async": true
}

更高级的通知(包含命令内容):

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
cmd = d.get('tool_input', {}).get('command', '')
print(cmd[:50] + '...' if len(cmd) > 50 else cmd)
")

# macOS 通知
osascript -e "display notification \"${COMMAND}\" with title \"Claude 要执行命令\" subtitle \"点击 Claude Code 查看详情\""
exit 0

常见陷阱和注意事项

陷阱 1:忘记让脚本可执行

chmod +x ~/.claude/hooks/*.sh

Hook 脚本必须有执行权限,否则会静默失败(取决于 command 字段的写法)。

陷阱 2:exit code 语义混淆

最容易犯的错误是把 exit code 1 误解为"拒绝":

exit 0 → 允许(正常情况)
exit 2 → 拒绝,错误消息发给 Claude
exit 1(或其他)→ 错误,stderr 只显示给用户,工具继续执行

如果想拒绝工具执行,必须使用 exit code 2,不是 1。

陷阱 3:同步 Hook 导致卡顿

耗时的操作(如网络请求、复杂计算)应使用 async: true

{
"type": "command",
"command": "curl -s https://slack.com/api/chat.postMessage ...",
"async": true // 不要让 Slack 通知阻塞工具执行!
}

陷阱 4:在 Hook 中调用 Claude Code

Hook 脚本不应该反过来调用 claude 命令,这会导致递归调用和死锁。

陷阱 5:忘记处理特殊字符

Hook 脚本通过 stdin 接收 JSON,当 JSON 包含路径、命令字符串时,要注意正确处理转义:

# 错误:直接用 jq 取值并插入 shell 可能导致注入
DIR=$(echo "$INPUT" | jq -r '.tool_input.file_path')
ls $DIR # 危险!

# 正确:引用变量
ls "$DIR"

陷阱 6:超时配置不合理

Hook 的默认超时时间是 60 秒(command 类型)。如果你的 Hook 是简单的 bash 脚本,建议设置较短的超时:

{
"type": "command",
"command": "bash ~/.claude/hooks/check.sh",
"timeout": 5 // 5 秒超时,防止 Hook 卡死影响体验
}

陷阱 7:全局 Hook 影响所有项目

用户级 ~/.claude/settings.json 中的 Hook 对所有项目生效。如果 Hook 逻辑依赖特定项目路径,应该放在项目级配置中。

禁用 Hooks 的方法

在某些场景下,你可能需要临时禁用 Hooks(例如排查问题):

通过 settings.json 禁用所有 Hooks:

{
"hooksConfig": {
"disableAllHooks": true
}
}

仅允许管理员管控的 Hooks:

{
"hooksConfig": {
"allowManagedHooksOnly": true
}
}

这个配置在企业环境中很有用:IT 管理员可以通过 managed-settings.json 强制部署 Hook,同时阻止用户添加自定义 Hook。

完整配置示例

下面是一个综合性的配置示例,集成了日志记录、安全检查和通知功能:

{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'echo \"[$(date -Iseconds)] PRE: $(cat)\" >> ~/.claude/audit.log'",
"async": true,
"timeout": 3
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/security-check.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'echo \"[$(date -Iseconds)] POST: $(cat | python3 -c \"import sys,json;d=json.load(sys.stdin);print(d.get(\\\"tool_name\\\",\\\"?\\\"))\")\" >> ~/.claude/audit.log'",
"async": true,
"timeout": 3
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '[$(date -Iseconds)] Session started' >> ~/.claude/audit.log",
"async": true
}
]
}
]
}
}

通过 Hooks 系统,Claude Code 的权限控制从"内置规则"扩展到了"用户定义规则",实现了真正灵活的安全策略定制。

📄source/src/schemas/hooks.tsL29-222查看源码 →
📄source/src/utils/settings/settings.tsL274-306查看源码 →
📄source/src/utils/hooks/hooksConfigManager.tsL26-100查看源码 →