免费

Claude Code Hooks 完整指南:让 Claude 的每个动作都在你掌控之中

完整解析 Claude Code Hooks 机制,从五种类型到退出码控制,配合四个真实配置案例,让 AI 操作可审计、可拦截、可自动化。


每次 Claude Code 读文件、写代码、执行命令,背后都有一套事件系统在运转。Hooks 就是接入这套系统的接口——你可以在任意时机注入自己的逻辑,自动做代码检查、记录日志、拦截危险操作,或者触发任何 shell 命令。

这篇文章把 Hooks 的机制、配置方式和实战用法完整讲一遍。


Hooks 是什么

Hooks 是配置在 settings.json 里的 shell 命令,Claude Code 在特定事件发生时自动执行它们。

最直接的类比:git hooks。git 在 commit、push 等操作前后可以触发脚本,Claude Code Hooks 的思路完全一样,只是触发点是 AI 的工具调用。

为什么这很重要?

Claude Code 的能力越强,你越需要确定性的控制层。Hooks 提供的是:
- 不依赖 prompt 的执行保证(Claude 可能忽略指令,但 hook 一定跑)
- 可审计的操作记录
- 自动化的质量检查


五种 Hook 类型

类型 触发时机 典型用途
PreToolUse 工具调用前 拦截危险操作、记录意图
PostToolUse 工具调用后 自动 lint、运行测试
PreCompact 上下文压缩前 保存当前状态快照
Notification Claude 发出通知时 桌面推送、Slack 消息
Stop Claude 完成回复时 汇总日志、触发后续流程

最常用的是 PreToolUse 和 PostToolUse,围绕工具调用做拦截和后处理。


配置格式

Hooks 写在 ~/.claude/settings.json(全局)或项目根目录的 .claude/settings.json(项目级)里:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint --silent"
          }
        ]
      }
    ]
  }
}

三个核心字段:

  • matcher:匹配工具名的正则,决定这个 hook 在哪些工具调用后触发。"Write|Edit" 表示写文件或编辑文件时触发。留空或 ".*" 匹配所有工具。
  • type:目前只有 "command"。
  • command:任意 shell 命令。

工具名参考

写 matcher 时需要知道工具叫什么名字:

工具名 对应操作
Write 写入新文件
Edit 编辑文件
Bash 执行 shell 命令
Read 读取文件
Glob 文件查找
Grep 内容搜索
TodoWrite 更新任务列表

Hook 的执行环境

执行时有几个环境变量可用,通过 stdin 传入 JSON:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.rb",
    "content": "..."
  },
  "tool_response": "..."
}

PreToolUse 拿到 tool_input(调用前的入参),PostToolUse 同时拿到 tool_response(工具返回结果)。

可以用这些数据写有判断逻辑的 hook:

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')

# 只对 .rb 文件运行 rubocop
if [[ "$file" == *.rb ]]; then
  rubocop "$file" --autocorrect-all --no-color
fi

退出码的含义

Hook 的退出码控制 Claude Code 的后续行为:

退出码 含义
0 成功,继续执行
2 阻断:取消当前工具调用,将 stderr 输出反馈给 Claude
其他非零 记录错误,但继续执行

退出码 2 是最有用的——它让你可以在 PreToolUse 里写拦截逻辑,阻止 Claude 执行某个操作,并告诉它原因。


实战:四个真实 Hook 配置

1. 写文件后自动格式化

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
          }
        ]
      }
    ]
  }
}

2. 禁止删除特定目录

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"禁止删除 migrations 或 seeds 目录\" >&2; exit 2; fi'"
          }
        ]
      }
    ]
  }
}

当 Claude 尝试 rm -rf db/migrations/ 时,hook 返回退出码 2,Claude 收到错误信息,操作被取消。

3. 操作日志

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

每次文件写入或命令执行都记录到 ~/.claude/audit.log,方便回溯。

4. 完成后桌面通知

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude 已完成\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

长任务跑完自动通知,不用盯着终端。(macOS 用 osascript,Linux 用 notify-send)


全局 vs 项目级

两个位置可以放 Hooks:

全局(~/.claude/settings.json):适合所有项目通用的规则,比如日志、通知。

项目级(.claude/settings.json):适合项目专属的检查,比如这个项目用 rubocop,那个项目用 eslint。项目级 hooks 和全局 hooks 会合并执行,不会互相覆盖。

团队项目建议把项目级 settings.json 提交到 git,这样所有人都跑相同的 hooks。


调试 Hook

Hook 不生效时的排查步骤:

  1. 确认 matcher 拼写:工具名区分大小写,write 不等于 Write
  2. 单独跑命令:把 hook 里的 command 复制出来在终端直接执行,确认它本身能跑通
  3. 看 Claude Code 输出:hooks 的 stderr 会显示在对话里(退出码 2 时尤其明显)
  4. 简化测试:先用 echo "hook triggered" >> /tmp/hook.log 确认触发,再加复杂逻辑

小结

Hooks 的核心价值是把不确定的 AI 行为和确定的工程规范接在一起。Claude 可能忘记运行测试,但 PostToolUse hook 不会。Claude 可能误删文件,但 PreToolUse 拦截不会放行。

从一个 hook 开始:写文件后自动 lint。跑顺了再加其他的。