首页 / Claude Code 入门教程 / Hooks 钩子

Claude Code 入门教程

Hooks 钩子

本教程共 34 篇 · 第 22 篇 · 更新于 2026-07-26 · 约 11 分钟阅读

Claude CodeClaude Code 入门教程钩子HookPreToolUsePostToolUse自动化

22. Hooks 钩子

本节目标:搞懂钩子(Hook)是什么、为什么比提示词约束更可靠。学会用 PreToolUse、PostToolUse、SessionStart 等事件,配匹配器过滤,用退出码和 JSON 控制行为。学完你能让某些操作「必定发生」—编辑后自动格式化、阻止改敏感文件、压缩后重注入上下文—不再赌 LLM 心情。

钩子解决什么问题

你可能在 CLAUDE.md 里写过「提交前跑测试」「别动 .env 文件」。问题是,这些只是建议,Claude 大概率会照做,但不是 100%。它哪天偷懒跳过测试,或者手滑改了不该改的文件,你拦不住。

钩子(Hook)就是把「建议」变成「硬规则」。它是你定义的 shell 命令,在 Claude Code 生命周期的特定节点确定性地执行。不依赖 LLM 选择,只要事件触发就一定跑。

打个比方:CLAUDE.md 里写「记得锁门」像贴张便签提醒,钩子像装了自动门锁—到点就锁,你忘不忘都一样。

钩子典型用途:

  • 强制规则:阻止 Claude 改 .envpackage-lock.json 等敏感文件
  • 自动化:编辑后自动跑 Prettier 格式化
  • 通知:Claude 等你输入时弹桌面通知
  • 上下文注入:压缩后重新塞入关键信息
  • 审计:记录 Claude 执行的每条命令
Warning

钩子用你当前系统环境的凭证(环境变量、用户权限)运行。恶意钩子可能泄露 API 密钥或误删文件。注册前务必逐行审查命令逻辑,别跑来路不明的脚本。

钩子放在哪配置

钩子写在 settings.jsonhooks 块里。位置决定生效范围:

位置范围能共享
~/.claude/settings.json你的所有项目否,本地
.claude/settings.json单个项目是,提交到仓库
.claude/settings.local.json单个项目否,gitignore
托管策略设置组织范围是,管理员控
插件 hooks/hooks.json启用插件时是,随插件
Skill/Agent frontmatter组件激活时是,定义在组件里

在 Claude Code 里输入 /hooks 能浏览所有已配置的钩子,按事件分组。这个菜单是只读的,增删改要直接编辑 JSON。

Tip

文件监视器通常会自动拾取设置文件的改动。如果改完 /hooks 没更新,重启会话强制重载。

钩子的基本结构

一个 hooks 块长这样:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

拆开看:

  • 事件名PostToolUse):什么时候触发
  • matcher"Edit|Write"):过滤条件,限定只对哪些工具/场景生效
  • hooks 数组:具体执行的命令列表
  • typecommand 跑 shell 命令(最常用),还有 httppromptagent
  • command:实际跑的 shell 命令

matcher 表示对该事件的所有情况触发。多个事件就并列写:

{
  "hooks": {
    "PostToolUse": [
      { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "..." }] }
    ],
    "Notification": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "..." }] }
    ]
  }
}

常用事件一览

Claude Code 在生命周期很多节点都留了钩子口。最常用的几个:

事件触发时机能否阻止
PreToolUse工具调用执行前
PostToolUse工具调用成功后否(已执行)
UserPromptSubmit你提交提示词,Claude 处理前能注入上下文
SessionStart会话开始或恢复能注入上下文
StopClaude 完成响应能让它继续干
NotificationClaude Code 发通知
ConfigChange配置文件在会话中变更
SubagentStart / SubagentStop子代理创建/结束-
PreCompact / PostCompact压缩前后-
SessionEnd会话结束-

完整列表还有 PermissionRequestPostToolUseFailurePostToolBatchFileChangedCwdChangedWorktreeCreate 等 30 来个。

Note

PreToolUse 在任何权限模式检查之前触发。返回 deny 的钩子能阻止工具,哪怕在 bypassPermissions 模式或用了 --dangerously-skip-permissions。这让你能强制执行用户绕不开的策略。但反过来,钩子返回 allow 不能绕过设置里的拒绝规则—钩子只能收紧,不能放松。

匹配器:精准过滤

不加 matcher,钩子对该事件的每次出现都触发。matcher 让你缩小范围。

对工具类事件(PreToolUsePostToolUse 等),matcher 匹配工具名

{
  "matcher": "Bash",
  "hooks": [{ "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt" }]
}

这只在 Claude 调 Bash 时记日志。用 |, 分隔多个工具:

"matcher": "Edit|Write"
"matcher": "Edit,Write"

不同事件 matcher 过滤的内容不一样:

事件matcher 过滤示例
PreToolUse / PostToolUse工具名BashEdit|Writemcp__.*
SessionStart会话如何启动startupresumeclearcompact
Notification通知类型permission_promptidle_prompt
ConfigChange配置源user_settingsproject_settingsskills
FileChanged文件名.envrc|.env

用 if 字段按参数过滤

matcher 只在工具名级别过滤。要按参数一起过滤,用 if 字段,语法和权限规则一样:

{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "if": "Bash(git *)",
      "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
    }
  ]
}

这只在 Claude 跑 git 命令时触发,其他 Bash 命令不触发。if 只对工具事件有效:PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied

Warning

if 过滤是尽力而为,Bash 命令解析失败时会开放(钩子照跑)。要硬性允许/拒绝,用权限系统,别用钩子。

钩子怎么收输入、给输出

钩子通过 stdin、stdout、stderr、退出码和 Claude Code 通信。

输入:stdin 收 JSON

事件触发时,Claude Code 把事件数据作为 JSON 喂给脚本的 stdin。比如 PreToolUse 在 Claude 跑 Bash 时收到:

{
  "session_id": "abc123",
  "cwd": "/Users/sarah/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

脚本解析这个 JSON,就能拿到工具名、命令、文件路径等,据此决定怎么做。

输出:退出码 + 文本

最简单的控制方式是退出码:

  • 退出 0:没意见,操作正常进行。对 UserPromptSubmitSessionStart,你写到 stdout 的内容会加进 Claude 上下文
  • 退出 2:阻止操作。写到 stderr 的原因会反馈给 Claude,让它调整
  • 其他退出码:操作继续,但成绩单显示 <hook name> hook error

一个阻止删表的例子:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q "drop table"; then
  echo "Blocked: 不允许删表" >&2
  exit 2
fi

exit 0

结构化 JSON 输出

退出码只能阻止或沉默。要更精细控制,退出 0 并往 stdout 打印 JSON:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "用 rg 代替 grep,性能更好"
  }
}

PreToolUsepermissionDecision 有四个值:

  • "allow":跳过交互式权限提示(但拒绝规则仍生效)
  • "deny":取消工具调用,原因反馈给 Claude
  • "ask":照常弹权限提示
  • "defer":非交互模式下保留工具调用供稍后处理
Note

用退出 2 + stderr 阻止,或用 JSON + 退出 0 做结构化控制。别混着用—退出 2 时 Claude Code 会忽略 JSON。

几个实战配置

编辑后自动格式化

Claude 每次编辑文件后自动跑 Prettier:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

jq 从输入 JSON 提取文件路径,xargs 把它传给 Prettier。

阻止改敏感文件

把检查逻辑放脚本里,更清晰。先建 .claude/hooks/protect-files.sh

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

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH 匹配受保护模式 '$pattern'" >&2
    exit 2
  fi
done

exit 0

macOS/Linux 上记得加可执行权限:

chmod +x .claude/hooks/protect-files.sh

然后注册 PreToolUse 钩子:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Claude 收到阻止反馈后会换路子,不会卡死。

压缩后重注入上下文

上下文压缩会丢细节。用 SessionStart 钩子在每次压缩后塞回关键信息:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo '提醒:用 Bun 不用 npm。提交前跑 bun test。当前迭代:auth 重构。'"
          }
        ]
      }
    ]
  }
}

echo 可以换成 git log --oneline -5 这种动态命令。如果只是会话开始时注入,用 CLAUDE.md 更合适。

Claude 等输入时弹通知

切到别的窗口干活,Claude 完成后通知你。Windows 上:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code 需要你', 'Claude Code')\""
          }
        ]
      }
    ]
  }
}

macOS 用 osascript,Linux 用 notify-send。matcher 可以细化到只在 idle_prompt(等输入)或 permission_prompt(等批准)时触发。

自动批准特定权限

每次计划做好都弹确认很烦。用 PermissionRequest 钩子自动批准 ExitPlanMode

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
          }
        ]
      }
    ]
  }
}
Warning

matcher 尽量窄。匹配 .* 或留空会自动批准所有权限提示,包括文件写入和 shell 命令,很危险。

多钩子怎么合并

同一事件配多个钩子时,每个钩子的命令都会跑完,Claude Code 再合并结果。一个钩子返回 deny 不会阻止兄弟钩子执行。

PreToolUse 权限决策,最严格的答案获胜,优先级:deny > defer > ask > allowadditionalContext 文本从每个钩子保留并一起传给 Claude。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "jq -r .tool_input.command >> ~/.claude/bash.log" },
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh" }
        ]
      }
    ]
  }
}

Claude 跑 rm -rf /tmp/build 时,两个钩子并行:日志钩子写日志后退出 0(无决策),防护钩子退出 2(拒绝)。拒绝获胜,命令被阻止,但日志已经写了。

不止 command:其他钩子类型

除了 "type": "command",还有几种:

"type": "prompt":基于提示的钩子。不跑 shell,而是把你的提示和钩子输入发给 Claude 模型(默认 Haiku)做是/否判断。返回 {"ok": true} 继续,{"ok": false, "reason": "..."} 阻止。适合需要判断而非硬规则的场景:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "检查所有任务是否完成。没完成就返回 {\"ok\": false, \"reason\": \"还剩什么\"}。"
          }
        ]
      }
    ]
  }
}

"type": "agent":基于代理的钩子(实验性)。生成一个子代理,能读文件、搜代码、用工具验证条件,再返回决策。比 prompt 钩子更强,默认超时 60 秒,最多 50 轮工具调用:

{
  "type": "agent",
  "prompt": "验证所有单元测试通过。运行测试套件检查结果。$ARGUMENTS",
  "timeout": 120
}

"type": "http":把事件数据 POST 到 HTTP 端点,端点用相同 JSON 格式返回结果。适合接外部审计服务、云函数。

Tip

钩子输入数据本身够做决策时用 prompt 钩子。要根据代码库实际状态验证时用 agent 钩子。需要确定性规则时用 command 钩子。

组件级钩子

Skill 和子代理的 frontmatter 里也能内嵌 hooks,只在对应组件激活时生效,组件执行完自动清理,不污染全局会话。

组件级钩子支持的 once: true(仅 Skill):设为 true 时,该钩子整个会话只跑一次,首次成功后自动移除。

组件级钩子和全局钩子会并行执行,互不冲突,匹配器规则和全局一致。

超时和限制

设计钩子要注意这些约束:

  • 超时command/http/mcp_tool 默认 10 分钟(UserPromptSubmit 降到 30 秒,MessageDisplay 降到 10 秒);prompt 30 秒;agent 60 秒。用 timeout 字段按钩子覆盖
  • PostToolUse 不能撤销:工具已经执行了,钩子只能反馈让 Claude 调整
  • PermissionRequest 不在非交互模式触发:自动化权限决策用 PreToolUse 代替
  • Stop 阻止上限:连续阻止 8 次没进展,Claude Code 会覆盖钩子。脚本要检查 stop_hook_active 字段,为 true 就提前退出 0

钩子不触发怎么排查

钩子配了不执行

  1. /hooks 确认钩子在正确事件下
  2. 检查 matcher 是否精确匹配工具名(区分大小写)
  3. 确认事件类型对:PreToolUse 在执行前,PostToolUse 在之后
  4. 非交互模式别用 PermissionRequest,换 PreToolUse

成绩单报 PreToolUse hook error

  • 脚本意外非零退出,手动测一下:
    echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
    echo $?
  • command not found 用绝对路径或 ${CLAUDE_PROJECT_DIR} 引用脚本
  • jq: command not found 装 jq,或改用 Python/Node 解析 JSON
  • 脚本没跑,macOS/Linux 上 chmod +x 加可执行权限

JSON 验证失败但 JSON 看着没错:shell 配置文件里的 echo 语句会污染输出。把 echo 包进交互式判断里:

if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

调试技巧:用 claude --debug-file /tmp/claude.log 启动,另一个终端 tail -f /tmp/claude.log 看完整执行详情—哪些钩子匹配、退出码、stdout、stderr 都有。会话中运行 /debug 也能开启日志并显示路径。

钩子 vs 技能 vs 权限

容易混的三个,理一下:

  • 权限规则:允许/拒绝哪些工具操作,是准入控制。最硬,但只能 allow/deny
  • 钩子(Hook):在生命周期节点跑命令,能做更复杂的事—格式化、通知、注入上下文、审计日志、阻止操作。确定性执行
  • 技能(Skill):给 Claude 额外指令和可执行命令,按需加载。影响 Claude「知道什么、怎么做」

简单说:权限管「能不能做」,钩子管「做了之后/之前自动干啥」,技能管「怎么做更好」。