免费

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 完成回复时 汇总日志、触发后续流程

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


配置格式

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。跑顺了再加其他的。