Claude Code Hooks 配置了但不触发:六步排查方法

Claude Code hooks 不触发通常有六个原因:配置文件位置错、matcher 写法错、脚本没有可执行权限、退出码不对、macOS 通知权限未授权,或 hook 触发了但命令本身失败。本文按优先级排查。

Claude Code Hooks 配置了但不触发:六步排查方法

Claude Code hooks 是在特定事件时自动运行 shell 命令的机制——比如每次文件编辑后自动格式化、在 Claude 等待输入时发桌面通知、在危险命令执行前拦截。配置写了但 hook 不触发,原因通常很具体,不是 Claude Code 本身的 bug,而是配置细节出了问题。

第一步:用 /hooks 确认 hook 是否已被加载

在 Claude Code CLI 里输入 /hooks,会打开一个 hook 浏览器,列出所有已加载的 hook 事件(每个有配置的事件旁边显示数量)。

如果你配置的 hook 没有出现在这里,说明配置根本没有被读取。常见原因:

  • 配置文件位置错了
  • JSON 格式有语法错误
  • 使用了错误的 hook 事件名

如果 hook 出现在列表里了,但还是没有触发,问题在 matcher 或脚本本身,继续下一步。

注意:/hooks 菜单是只读的,无法在菜单里修改配置,需要直接编辑 settings JSON 文件。

第二步:检查配置文件的位置

Claude Code 的 hooks 可以配置在两个位置,优先级不同:

文件位置 作用范围
~/.claude/settings.json 全局,适用于所有项目
[项目目录]/.claude/settings.json 仅当前项目

最常见的错误:把项目级的 settings.json 放在了错误的目录(比如放在项目根目录而不是 .claude/ 子目录里)。确认路径是 .claude/settings.json,不是直接在项目根目录的 settings.json

正确的 JSON 结构:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}

注意:hooks 是顶层键,事件名(PostToolUseNotification 等)是 hooks 对象的子键。结构嵌套方式容易出错。

第三步:验证 matcher 写法

Matcher 决定 hook 在哪个工具调用时触发。写法错误会导致 hook 注册成功但永远不匹配:

PreToolUse / PostToolUse 的 matcher 是工具名,支持 | 分隔多个(Claude Code v2.1.191+ 也支持 ,):

  • 匹配所有文件编辑:"Edit|Write"
  • 只匹配 Bash 执行:"Bash"
  • 匹配所有工具(空字符串):""

Notification 的 matcher 是通知类型,不是工具名:

  • 所有通知:"" 或空
  • 只在等待输入时:"idle_prompt"
  • 只在需要授权时:"permission_prompt"

常见错误:在 Notification hook 里写了 "Edit|Write" 这样的工具名 matcher,导致永远不触发。

第四步:检查脚本的可执行权限

如果 hook command 调用的是一个脚本文件(而不是内联命令),脚本必须有可执行权限:

chmod +x .claude/hooks/your-script.sh

忘记 chmod +x 是最容易犯也最难察觉的错误之一,因为 Claude Code 不会报"权限不足"的明显错误,hook 只是默默不运行。

确认脚本在终端里能单独执行:

echo '{"tool_input":{"file_path":"test.js"}}' | .claude/hooks/your-script.sh

能运行、退出码符合预期才算配置正确。

第五步:确认退出码语义

Claude Code hooks 的退出码有明确的语义,写错会导致预期之外的行为:

退出码 含义 适用场景
0 成功,继续执行 格式化、日志、通知
1 失败,向 Claude 报告错误消息(stderr) 警告但不阻断
2 阻断——不执行这次工具调用 PreToolUse 里阻止危险命令

典型错误场景:

  • 写了一个 PreToolUse 文件保护 hook,想拦截对 .env 的修改,但脚本在匹配时返回了 exit 1 而不是 exit 2——结果是 Claude 收到了警告但仍然继续修改文件。
  • PostToolUse 里写了一个格式化 hook,格式化命令失败时返回非零退出码,Claude 把这解读为格式化出了问题,在 session 里产生了不必要的错误信息。

第六步:macOS Notification Hook 的特殊权限问题

如果你配置了 macOS 桌面通知 hook,用的是 osascript,通知不出现通常是系统权限问题,不是 Claude Code 问题:

osascript 通过 Script Editor 发通知,Script Editor 需要在系统设置里单独授权通知权限。这个权限不会自动提示。

修复步骤:

# 在 terminal 里运行一次,触发权限检查
osascript -e 'display notification "test"'

运行后什么都不会出现。打开 系统设置 → 通知,找到 Script Editor,开启"允许通知"。再运行一次上面的命令,确认通知出现。

配置好权限后,Claude Code 的 Notification hook 才能正常工作。

如何验证 hook 实际运行了什么

在 hook command 里加日志是最直接的排查方法:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): hook triggered for $(jq -r '.tool_input.file_path')\" >> /tmp/claude-hooks.log && npx prettier --write \"$(jq -r '.tool_input.file_path')\""
          }
        ]
      }
    ]
  }
}

这样每次 hook 触发都会在 /tmp/claude-hooks.log 里留一条记录,可以看到触发时间和文件名,判断 hook 是触发了但命令失败,还是根本没触发。

常见问题

问:在 .claude/settings.json 里配置了 hooks,但项目里没有 .claude 目录,怎么办? 答:手动创建 .claude 目录和 settings.json 文件:mkdir -p .claude && echo '{"hooks":{}}' > .claude/settings.json,然后编辑这个文件加入你的 hooks 配置。

问:hook 的命令可以用相对路径吗? 答:可以,相对路径相对于项目根目录($CLAUDE_PROJECT_DIR)解析。官方推荐的写法是 "$CLAUDE_PROJECT_DIR"/.claude/hooks/your-script.sh,用环境变量明确路径。

问:能在一个事件上挂多个 hooks 吗? 答:可以。同一个事件的 hooks 数组可以有多个条目,按顺序执行。如果其中一个返回 exit 2,后续的 hooks 不会执行,工具调用被阻断。

问:hooks 在 subagent 里也会触发吗? 答:会。subagent 运行期间的工具调用(Edit、Write、Bash 等)同样会触发 hooks,包括项目级和全局级的配置。

参考资料