Claude Code How-To Guide

Claude How To

Hooks 指南

hooks 是 Claude Code 的“事件触发自动化”机制。它允许你在某些时机自动做点事情,例如:

  • 调工具前先检查风险
  • 写完文件后自动格式化
  • 提交前跑测试
  • 结束前记录日志或上下文

如果你已经会 slash commands 和 CLAUDE.md,下一步最值得掌握的自动化能力通常就是 hooks。


hooks 是什么

你可以把 hook 理解成一条规则:

  1. 某个事件发生
  2. 匹配某个工具或场景
  3. 自动执行一个动作

这些动作可以是:

  • shell command
  • HTTP webhook
  • prompt 型判断
  • agent 型评估

hooks 最大的价值,是把“你本来每次都要手动做的检查”变成自动流程。


常见配置位置

  • ~/.claude/settings.json:用户级,对所有项目生效
  • .claude/settings.json:项目级,适合团队共享
  • .claude/settings.local.json:本地项目配置,不建议提交
  • plugin 内的 hooks/hooks.json
  • 某些 skill / subagent frontmatter 内的 component-scoped hooks

如果你是新手,建议先从用户级或项目级配置开始。

从 v2.1.218+ 起,project agent frontmatter 里的 hooks 只有在 agent 文件所在目录通过 workspace trust 后才运行。这个限制不会因为 hook 写在 frontmatter 里就自动绕过。


基本结构

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

关键字段说明

字段 作用 示例
hooks hook 顶层配置入口 { "PreToolUse": [...] }
matcher 匹配工具名或模式 "Write"、"Edit|Write"、"Write,Edit"、"*"
type hook 类型 "command"、"http"、"mcp_tool"、"prompt"、"agent"
command 执行的 shell 命令 "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
timeout 超时秒数 30
once 每会话只跑一次 true

注意:这些字段属于配置协议的一部分,不要为了中文化把它们翻掉。


用 if 按工具参数继续收窄匹配

matcher 负责匹配工具名,例如 "Write"、"Edit|Write" 或 "*"。 从 v2.1.172+ 起,如果你还想按工具参数继续过滤,比如只在修改 src/ 目录时跑 lint,或只在读取 .env 时做拦截,可以在具体 hook handler 上加 if。

if 使用 permission-rule 语法,也就是 ToolName(pattern) 这种形式。常见例子:

  • Edit(src/**):只匹配 src/ 下的编辑
  • Read(.env):匹配当前目录及子目录里的 .env
  • Read(~/.ssh/**):匹配用户 SSH 目录读取
  • Bash(git push *):只匹配 git push 相关命令

v2.1.214+ 范围变化:在 hook if 条件里,单段 dir/**(例如 Edit(src/**))现在只匹配 <cwd>/dir,不会再匹配任意深度的 foo/dir/**。如果确实要匹配任意深度,请写 **/dir/**。这个收窄只影响 hook if 和 allow-rule 自动批准;deny / ask permission rules 里的 dir/** 仍按任意深度理解。

注意位置:if 是 hooks 数组里某个 handler 的字段,和 type、command 同级,不是写在 matcher 上。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(src/**)",
            "command": "./hooks/lint-src.sh"
          }
        ]
      },
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "if": "Read(.env)",
            "command": "./hooks/block-secret-read.sh"
          }
        ]
      }
    ]
  }
}

这里的 if、matcher、type、command 都是配置 key,不能为了中文化改名。


matcher 怎么用

形式 含义 例子
精确匹配 只匹配某个工具 "Write"
正则匹配 匹配多个工具 "Edit|Write"
逗号列表 匹配任一列出的工具;v2.1.191+ 起生效 "Write,Edit"
全匹配 匹配全部工具 "*" 或 ""
MCP 工具模式 匹配 MCP 工具 "mcp__memory__.*"

从 v2.1.195+ 起,matcher 会按更精确的规则匹配工具名。带连字符的 MCP tool name 不会再因为子串重叠而误触发另一个工具;"Write,Edit" 这类逗号列表会按“任一工具命中”处理,而不是像旧版本那样静默不触发。

如果你配的是 InstructionsLoaded,它还有几种常见 matcher 值:

matcher 值 含义
session_start 会话刚启动时加载的 instructions
nested_traversal 向下遍历目录时加载的 instructions
path_glob_match 通过 path glob 匹配加载的 instructions

如果你不确定先配什么,最常见的起点是:

  • Bash
  • Write
  • Edit|Write

五种 hook 类型

1. command(本地命令)

最常见的类型。适合:

  • shell 校验
  • 安全扫描
  • 自动格式化
  • 日志记录
{
  "type": "command",
  "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
  "timeout": 60
}

args 形式:直接 exec,不走 shell

从 v2.1.139 开始,command hook 还支持 args 形式,直接用 execve() 拉起目标二进制,不经过 shell 解析。

2. http(HTTP 调用)

适合把事件发给 webhook 或外部系统。

{
  "type": "http",
  "url": "https://example.com/hook"
}

continueOnBlock:让 Claude 看到 hook 的阻断反馈后再调整

从 v2.1.139 开始,PostToolUse hook 可以配 continueOnBlock: true。默认情况下 hook 返回 "decision": "block" 会直接终止当前 turn;加上这个字段后,Claude 会把阻断原因当成一个 tool_result 读回来,再决定如何重试或换方案。

常见用途:

  • 通知系统
  • 团队消息流
  • 外部审计系统

3. mcp_tool(MCP 工具调用)

这是上游在 v2.1.118 明确补进来的 hook 类型,适合:

  • 你已经把验证逻辑做成 MCP server
  • 不想每次再绕一层 shell
  • 希望 hook 直接调用 MCP tool

典型配置会直接指定:

  • server
  • tool

而不是本地 command 或远程 url。

4. prompt(提示词)

让模型根据 prompt 判断是否该继续,常见于:

  • 任务完成检查
  • 结束前质量判断
  • prompt 合规性判断

5. agent(代理)

让 Claude 用独立 agent 做更复杂的评估,适合:

  • 架构规则检查
  • 多步验证
  • 比较复杂的质量门禁

常见事件

当前最值得先掌握的事件:

事件 什么时候触发 最常见用途
PreToolUse 工具执行前 校验、阻止、改输入
PostToolUse 工具执行后 验证、补上下文、记录
Setup 每个 session 的一次性初始化阶段 安装依赖、准备工具、生成临时环境
UserPromptSubmit 用户提交 prompt 时 prompt 校验
Stop / SubagentStop Claude / subagent 结束时 完成度判断
SessionStart / SessionEnd 会话开始 / 结束 初始化、清理、日志

更完整的生态还包括:

  • PermissionRequest
  • PermissionDenied
  • PostToolUseFailure
  • UserPromptExpansion
  • PostToolBatch
  • Notification
  • MessageDisplay
  • DirectoryAdded
  • TaskCreated
  • TaskCompleted
  • CwdChanged
  • WorktreeCreate
  • WorktreeRemove

Notification 的 matcher 还新增了两个和后台 agent 有关的值(v2.1.198+):

  • agent_needs_input:后台 agent 需要用户补充输入
  • agent_completed:后台 agent 已完成

如果你是新手,不需要一上来把所有事件都学完。

截至 v2.1.219+,上游共有 31 个 hook 事件、5 种 hook 类型。类型(command、http、mcp_tool、prompt、agent)决定 hook 如何运行,事件决定它何时运行,两者不是同一个分类轴。如果你还在参考旧资料里的 25、28、29 或 30 个事件,优先以当前版本为准。MessageDisplay 会在 assistant message text 显示过程中触发;新增的 DirectoryAdded 会在 /add-dir 或 SDK register_repo_root 注册新工作目录后触发。


最实用的三个起步场景

场景 1:提交前跑测试

这是最容易感受到 hooks 价值的起点。

mkdir -p ~/.claude/hooks
cp 06-hooks/pre-commit.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/pre-commit.sh

常见配置思路:

  • 监听 PreToolUse
  • matcher 设为 Bash
  • 在脚本里判断是否是 git commit

场景 2:写完文件自动格式化

适合在 PostToolUse + Write|Edit 场景下做代码格式化或轻量校验。

场景 3:安全扫描

适合在文件修改后,自动扫明显危险模式,例如 secrets、危险命令或敏感字符串。


hook 的输入输出是怎么工作的

输入

hooks 通常通过 stdin 接收 JSON 输入。

所有 hooks 都会收到 session、工作目录和事件等基础字段。v2.1.196+ 起还会收到 prompt_id,它是当前 prompt 的 UUID,可与 OpenTelemetry 的 prompt.id 属性关联,用来串联一次用户输入、hook 日志和 telemetry。

输出

可以通过:

  • exit code
  • stdout JSON

把结果反馈回 Claude。

常见控制方式包括:

  • allow
  • deny
  • ask
  • defer
  • updatedInput
  • additionalContext
  • hookSpecificOutput.updatedToolOutput

shell hook 的退出码不是普通脚本里的“任意非零都算阻断”:0 表示成功;只有 exit 2 才会按事件语义阻断,并且阻断原因必须写到 stderr。其他非零退出码只是非阻断错误,例如 PreToolUse 中用 exit 1 不会阻止工具调用。PostToolUse 已发生在工具执行之后,exit 2 只能把 stderr 反馈给 Claude,不能撤销已经完成的操作。

PreToolUse 的 permissionDecision 可返回 allow、deny、ask 或 defer。defer 用于把工具调用留待稍后恢复,并会忽略 permissionDecisionReason、updatedInput 和 additionalContext。多个 hook 意见冲突时,优先级是 deny > defer > ask > allow;permission rules 中的 deny / ask 仍会继续参与判断。

hooks 现在也能感知当前 effort level

从 v2.1.133+ 开始,hook 输入和 Bash 子进程环境里都能拿到当前 effort 信息。 这对你想做“高 effort 时多跑一层校验,低 effort 时只做轻量检查”的场景很有用。

你可以关注两类入口:

  • hook 输入 JSON 里的 effort.level
  • Bash 子进程环境变量里的 CLAUDE_EFFORT

另外还有一个对排错很实用的环境变量:

  • CLAUDE_CODE_SESSION_ID:可用来把 Bash 日志和 hook telemetry 对到同一个 session

不要依赖未列入官方 reference 的 Stop / SubagentStop 字段

上一轮同步曾按 release notes 记录过 background_tasks 和 session_crons,但它们没有出现在当前官方 hooks reference 的字段列表里。为了避免中文用户写出依赖不稳定输入的 hook,本版移除了这两个字段的用法说明。

如果你想在 session 结束前做兜底检查,更稳的做法是让 hook 读取你自己维护的状态文件、测试锁文件或 CI 状态,而不是假设 Stop / SubagentStop 输入一定包含这两个数组。

updatedToolOutput 现在不只限 MCP 工具

上游在 v2.1.121+ 扩大了 hookSpecificOutput.updatedToolOutput 的适用范围: 它现在对 Bash、Edit、Read 等普通工具也生效,不再只是 MCP tool。

这意味着 PostToolUse hook 可以在 Claude 看到工具输出前先做一层处理,例如:

  • 去掉命令输出里的 ANSI 颜色码
  • 过滤噪音日志
  • 脱敏 secrets
  • 把复杂 diff 改成更容易读的摘要

返回结构保持 key 原样,不要翻译:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "updatedToolOutput": "<plain-text output>"
  }
}

SessionStart 可以重扫 skills 并设置 session 标题

从 v2.1.152+ 开始,SessionStart hook 可以通过 stdout JSON 做两件很实用的事:

{
  "reloadSkills": true,
  "hookSpecificOutput": {
    "sessionTitle": "Payments migration"
  }
}
  • reloadSkills: true:在当前 session 里重新扫描 skills,效果类似 /reload-skills
  • hookSpecificOutput.sessionTitle:设置 session 启动或恢复时显示的标题

这些 key 是协议字段,不要翻译。

SessionStart 的 matcher / source 还包括 startup、resume、clear、compact 和 fork。从 v2.1.214+ 起,fork 出来的 session 会报告 "fork",不再伪装成 "resume"。

另外,status-line command scripts 现在会收到 COLUMNS 和 LINES 环境变量。你可以根据终端宽度输出短状态栏或详细状态栏,例如窄窗口只显示分支和测试状态,宽窗口再补充 token / session 信息。

这轮上游同步后要特别注意什么

上游最近把一批示例 shell hooks 统一到了 stdin JSON 协议,并补了 Windows Git Bash 兼容性。

这意味着你现在更应该按这种方式理解示例脚本:

  • 从 stdin 读 JSON 输入
  • 用 file_path、command、user_prompt 这类字段取值
  • 需要阻止或修改行为时,返回 Claude Code 认可的 stdout JSON;简单阻断则向 stderr 输出理由并 exit 2

如果你还在按“第一个位置参数就是文件路径”来写,很容易和新版本示例脱节。

另外,v2.1.119 之后 PowerShell 侧的 auto-approve 行为也更接近 Bash 了。
如果你在 Windows 上跑 Claude Code,这意味着权限模式的体验比以前更一致。

还有一个安全修复值得单独记住:v2.1.145 关闭了 Bash 裸环境变量 allowlist 的自动批准漏洞。以前如果只允许了 FOO=bar,某些 FOO=bar somecommand 形式的命令可能被误认为已允许;现在这类命令会重新触发权限提示。需要自动批准时,请写覆盖完整命令的 Bash(...) 规则,而不是只允许环境变量赋值本身。

从 v2.1.163+ 起,Stop / SubagentStop hook 还可以返回 hookSpecificOutput.additionalContext,把上下文补给 Claude 并继续当前 turn,而不是用 "decision": "block" 这种容易显示成错误标签的方式“硬拦住”。

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "请先运行测试,再说明任务完成。"
  }
}

这里的 hookSpecificOutput、hookEventName、additionalContext、Stop 都是协议字段,不要翻译。


本目录示例脚本怎么用

文件 用途 适合什么时候先试
pre-commit.sh 提交前跑测试 第一个推荐示例
format-code.sh 自动格式化 写代码后自动收尾
security-scan.sh 安全扫描 团队规范较严格时
pre-tool-check.sh Bash 高风险命令预检查 想先拦截危险命令时
validate-prompt.sh prompt 校验 控制输入质量
log-bash.sh 记录命令使用 做审计或追踪
dependency-check.sh 依赖清单变更后做漏洞扫描 团队依赖治理较严格时
notify-team.sh 通知团队 配合外部消息系统
context-tracker.py 上下文追踪 调试长会话问题
session-end.sh 会话结束时记录学习模块 想跟踪自己学到哪一章时

关于 auto-adapt-mode 的更新

如果你之前看过旧资料,可能见过一种思路:

  • 在 PostToolUse 里记录你批准过的命令
  • 自动把这些批准“泛化”进本地权限配置

上游最近已经把这条路线下掉了,不再推荐继续使用旧的:

  • 06-hooks/auto-adapt-mode.py

新的建议方式是:

  • 使用一次性脚本 09-advanced-features/setup-auto-mode-permissions.py
  • 直接把一组更保守、更可控的权限规则写进 ~/.claude/settings.json
  • 再通过命令行参数按需放开 edits、tests、git writes、package installs、GitHub write 等能力

这样做的好处是:

  • 不再依赖“边用边学你的批准”
  • 配置更可预测
  • 更适合团队分享和审阅
  • 对中国用户来说,也更容易解释“当前到底开了哪些权限”

如果你在意 Auto Mode 但又没有 Team plan,优先看:


SessionEnd 学习进度记录

上游这次新增了一个更贴近学习仓库定位的 hook:06-hooks/session-end.sh。

它会在 Claude Code 会话结束时提示你输入本次学习过的模块,例如:

06,07

然后把记录追加到:

~/.claude-howto-progress.json

这个路径放在仓库外面,所以你之后 git pull 或同步上游时,不会把自己的学习记录覆盖掉。

为什么用 SessionEnd,不是 Stop

  • Stop:Claude 每次回复结束后都会触发
  • SessionEnd:整场会话结束时触发一次

如果目标是写“今天学了哪些模块”的学习日志,SessionEnd 更自然。否则每一轮对话后都弹一次输入,体验会很烦。

为什么脚本里要用 /dev/tty

hook 的 stdin 默认已经被 Claude Code 用来传 JSON payload。 如果脚本还想和用户交互,read 就不能直接读 stdin,而要显式从 /dev/tty 读:

read -r INPUT </dev/tty

这也是写交互式 hooks 时很容易踩的坑。

安装方式

mkdir -p .claude/hooks
cp 06-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh

然后在 .claude/settings.json 里加入:

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\""
          }
        ]
      }
    ]
  }
}

输出示例:

{
  "sessions": [
    {
      "date": "2026-04-28",
      "time": "09:18",
      "modules": ["06-hooks", "07-plugins"],
      "notes": "试了 SessionEnd hook,并理解了 /dev/tty 的用途"
    }
  ]
}

配套的本地进度页面

如果你更喜欢可视化勾选,可以直接打开:

open local-progress/index.html

这个页面覆盖 10 个模块,进度保存在浏览器 localStorage,不会写进仓库。 导出的 JSON 会被 .gitignore 忽略,适合自己备份,不适合提交。


hooks 配置里哪些绝对不能翻

  • hooks
  • matcher
  • type
  • command
  • timeout
  • reloadSkills
  • sessionTitle
  • MessageDisplay
  • DirectoryAdded
  • COLUMNS
  • LINES
  • 事件名,例如 PreToolUse
  • JSON key
  • 实际命令行片段

可以翻译:

  • 注释
  • 使用说明
  • README 正文

不能翻译:

  • 协议字段
  • 事件名
  • 命令

中国用户特别注意

1. shell 差异

很多示例默认更偏 Unix / macOS / Linux 风格。 Windows 用户请先确认你当前用的是:

  • PowerShell
  • Git Bash
  • WSL

2. 环境依赖

如果 hook 里调用:

  • python
  • node
  • uv
  • npm
  • pytest

请先确认本机路径和环境变量,否则“配置看起来对,运行却没效果”非常常见。

如果你在 Windows 上用 Git Bash,这一点更重要。上游最近的脚本更新,核心目的之一就是避免脚本只在 macOS / Linux 上能跑。

3. 网络与代理

如果 hook 会发 HTTP 请求,记得考虑:

  • 公司代理
  • TLS 证书
  • 外部服务可访问性

debugging 和排错思路

如果 hook 没生效,优先按这个顺序排查:

  1. 事件名是否正确
  2. matcher 是否匹配到了目标工具
  3. 路径是否正确
  4. 脚本是否可执行
  5. 脚本内部依赖是否存在
  6. stdout / exit code 是否符合预期

如果 hook 触发了但效果不对,重点看:

  • 命令是否真的执行成功
  • 你的 hook 是否过重、太慢
  • 是否在错误事件上绑定了错误的逻辑

最佳实践

  • 从一个轻量 hook 开始,不要一口气加很多
  • 优先做“高频、确定、低风险”的自动动作
  • 不要让 hook 变成新的复杂系统
  • 先让 hook 稳,再考虑把它放进 plugin
  • 对中国用户来说,环境说明和 shell 差异提示非常重要

推荐下一步

  • 想做自动触发的复用能力:看 03-skills
  • 想接入外部系统:看 05-mcp
  • 想把一整套流程打包分发:看 07-plugins

Content rendered from Hooks 指南 on GitHub. Markdown is the single source of truth — re-run scripts/build_website.py after editing to refresh the site.