环境、压缩与模型事件
ConfigChange、CwdChanged、DirectoryAdded、FileChanged、WorktreeCreate/Remove、PreCompact/PostCompact、PreModelSwitch/PostModelSwitch、SessionEnd、Elicitation/ElicitationResult。
ConfigChange
会话期间配置文件变化时运行:用于审计设置变更、强制安全策略或阻止对配置文件的未授权修改。设置文件、托管策略文件或 Skill 文件变化时触发;托管策略只在 managed-settings.json 或 managed-settings.d/ 里的文件变化时才触发。matcher 按配置来源过滤:
| matcher | 触发 |
|---|---|
user_settings | ~/.claude/settings.json 变化 |
project_settings | .claude/settings.json 变化 |
local_settings | .claude/settings.local.json 变化 |
policy_settings | managed-settings.json 或 managed-settings.d/ 里的文件变化 |
skills | .claude/skills/ 里的 Skill 文件变化 |
输入:source(哪类配置变了)和可选 file_path。决策控制:退出 2 或 JSON decision: "block" 阻止变更生效(新设置不会应用到运行中的会话);reason 被接受但从不显示。policy_settings 的变更不能被阻止,Hook 仍会触发,可用来记录这些编辑,但任何阻止决定都被忽略。被阻止的变更不会向你或 Claude 展示任何消息,不论你是用退出码还是 JSON 阻止的;Claude Code 只采纳阻止决定,丢弃 systemMessage 和 continue。
CwdChanged
主对话里的 shell 命令改变了工作目录(如 Claude 执行了 cd)时运行:用于重载环境变量、激活项目特定工具链、自动运行 setup 脚本,与 FileChanged 配合适合 direnv 这类按目录管理环境的工具。可用 CLAUDE_ENV_FILE:写入其中的变量对后续 Bash 命令持久生效,直到下一次 CwdChanged。不支持 matcher,每次都触发。输入:old_cwd、new_cwd。输出:可返回 watchPaths(绝对路径数组)动态设置 FileChanged 监视的路径,替换当前动态监视列表(matcher 配置里的路径始终被监视,返回空数组清除动态列表);没有决策控制,不能阻止目录变化;Claude Code 读取 watchPaths 和 systemMessage(交互会话里显示为简短终端通知),丢弃 continue。
DirectoryAdded
你在会话中途用 /add-dir 添加工作目录、或 SDK 客户端用 register_repo_root 控制请求添加之后运行;用于准备新加入的仓库(如安装其依赖)。不触发的情况:用 --add-dir 启动标志传入目录(那些由 SessionStart 覆盖)、在 /permissions 的 Workspace 标签页添加、添加的目录已经是工作目录或在其内。在刷新沙箱和权限状态之后触发,所以沙箱工具在 Hook 运行时已能看到新目录;Hook 命令本身不在沙箱里运行。Claude Code 不等待 Hook:添加立即完成,Hook 在后台以默认 600 秒超时运行。matcher:slash_command(/add-dir)、register_repo_root(SDK 控制请求)。输入:directory(添加的绝对路径)、source。无决策控制;slash_command 来源时 Hook 的 systemMessage 作为上下文在下一轮交给 Claude(转录里显示失败 Hook 的计数);register_repo_root 来源时输出只写入调试日志。
FileChanged
被监视的文件在磁盘上变化时运行。Claude Code 用文件系统监视器而不是检查工具调用来检测变化,所以不论谁改的都会运行:Edit/Write 工具调用、Claude 用 Bash 运行的脚本,或 Claude Code 之外的进程。常见用途是项目配置文件变化时重载环境变量。matcher 起两个作用:构建监视列表——值按 | 拆分,每段作为工作目录里的字面文件名注册(".envrc|.env" 恰好监视这两个文件,正则模式不能用来构建监视列表);过滤运行哪些 Hook——文件变化时,同一个值按标准 matcher 规则对变化文件的文件名(basename)过滤 Hook 组。例如在 data.csv 被任何方式改写后规范化它的换行符。Hook 从 stdin JSON 的 file_path 读取变化文件的绝对路径。想监视事先无法点名的文件,可以从 Hook 返回 watchPaths 动态更新监视列表;可使用 CLAUDE_ENV_FILE。输入:file_path、event("change" 修改、"add" 创建、"unlink" 删除)。输出:watchPaths(替换当前动态监视列表);无决策控制,不能阻止变化;Claude Code 读取 watchPaths 和 systemMessage,丢弃 continue。
WorktreeCreate
创建 worktree 时运行:来自 claude --worktree、使用 isolation: "worktree" 的子智能体、或 Claude Code 为其隔离在独立 worktree 里的后台会话。默认用 git worktree 创建隔离副本;配置 WorktreeCreate Hook 会完全取代默认的 git 行为,让你可以用 SVN、Perforce、Mercurial 等别的版本控制系统;因此 .worktreeinclude 不被处理,需要复制本地配置文件时要在 Hook 里自己做。Hook 必须返回所创建 worktree 目录的路径,Claude Code 把它作为隔离会话的工作目录。输入:name(新 worktree 的 slug 标识,用户指定或自动生成)。输出不用标准的允许/阻止模型,而由成功与否决定结果:命令型 Hook 把路径作为 stdout 的最后一个非空行打印(Claude Code 会先去掉 ANSI 转义,所以 shell 启动横幅不碍事);HTTP Hook 在响应体里返回 {"hookSpecificOutput": {"hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path"}}。Hook 失败或没有产出路径则 worktree 创建失败。相对路径按 Hook 运行目录解析并折叠 ./..;解析结果不是可进入的目录会报错;含 . 或 .. 段的绝对路径、以及穿过仓库根目录之下符号链接的路径会被拒绝(因为仓库里提交的符号链接可能把操作重定向到别处)。
WorktreeRemove
worktree 被移除时运行,是 WorktreeCreate 的清理对应:触发于你退出 --worktree 会话并选择移除、isolation: "worktree" 的子智能体完成、或你删除其 worktree 由 Hook 创建的后台会话。git 类 worktree 由 Claude Code 自动用 git worktree remove 清理;配置了 WorktreeCreate 的要配对一个 WorktreeRemove 来控制清理。没有 WorktreeRemove Hook 时,退出 --worktree 会话并选择移除,会回退到对你的 WorktreeCreate Hook 返回的路径运行 git worktree remove --force;Hook 退出 0 即视为已移除(Claude Code 不再读取别的,所以确保你的 Hook 真的删了目录);退出非零且 worktree_path 处的目录之后仍存在,则移除失败,worktree 留在磁盘上且没有 git 回退(命令和 stderr 进调试日志;若在删除后台会话,会话也保留,并在智能体视图里报告 Hook 的结束情况)。Claude Code 从不删除由 Hook 创建的 worktree 所属的分支(它只知道路径),如果 WorktreeCreate Hook 创建了分支,要在 WorktreeRemove 里自己删。删除后台会话时会先验证存储的 worktree 路径,拒绝符号链接或穿过仓库根目录之下符号链接的路径。输入:worktree_path(被移除 worktree 的绝对路径,即 WorktreeCreate 返回的路径)。JSON 输出字段被丢弃。
PreCompact 与 PostCompact
PreCompact 在 Claude Code 即将压缩之前运行。matcher:manual(/compact)、auto(对话达到自动压缩窗口时的自动压缩)。输入:trigger、custom_instructions(手动时是用户传给 /compact 的内容)。退出 2 或 JSON "decision": "block" 阻止压缩(手动 /compact 时 stderr 消息显示给用户;阻止自动压缩的效果取决于触发时机:在上下文上限之前主动触发的会被跳过、对话照常继续,已到上限时的影响见官方说明);systemMessage 和 continue 被丢弃。PostCompact 在压缩完成后运行,用于对新的压缩状态做出反应(如记录生成的摘要或更新外部状态);matcher 同上;输入:trigger、compact_summary(压缩生成的对话摘要);无决策控制,不能影响压缩结果;systemMessage 和 continue 被丢弃。
PreModelSwitch 与 PostModelSwitch
(需要 Claude Code v2.1.251 或更高。)PreModelSwitch 在 Claude Code 应用你或客户端请求的模型切换之前运行:用于阻止切换、要求确认、或在切换发生前显示它的成本。针对这些请求运行:/model <name> 和 /model 选择器、Option+P/Alt+P 模型选择器、/config 里的 Model 设置、启用 fast mode 且这改变了会话模型、Agent SDK 宿主或 Remote Control 的 set_model(或 apply_flag_settings 里的模型变更)请求。它不对 Claude Code 自己做的切换运行(如自动模型回退、恢复会话时还原模型)。matcher 与目标模型的规范名比较(忽略 [1m] 后缀;别名 opus、带日期的模型 ID 和服务商特定 ID 解析到同一个名字);无法确定规范名时(如只有你的 LLM 网关认识的自定义模型 ID),无论 matcher 都运行每个 PreModelSwitch Hook;matcher 可写成精确名、| 分隔列表(claude-opus-4-6|claude-opus-5)或正则(.*opus.*)。输入:from_model、to_model、requested_model(请求里写的别名或完整 ID,请求默认模型时为 null)、source("command"、"picker"、"sdk")、context_tokens(下一次请求重新发送的 token 数)、prompt_cache_warm(当前模型的提示缓存是否可能仍热,切换会丢弃它)、cache_ttl("5m" 或 "1h")、estimated_cache_write_usd、pricing("configured"/"catalog"/"default");后五个描述把对话重新发给新模型的成本。决策控制:退出 2 或顶层 decision: "block" 取消切换(Claude Code 保留当前模型并报告被 PreModelSwitch Hook 阻止,显示你的 stderr);更细的控制用 hookSpecificOutput 的 permissionDecision("allow" 继续并跳过缓存热时显示的确认;"deny" 取消;"ask" 请用户确认)和 permissionDecisionReason(deny 时显示给用户,或作为 set_model 请求的错误;ask 时显示在确认提示里)。只有交互会话里的 /model 能显示 "ask" 提示,其他界面(含 -p 非交互、/config、set_model 请求)一律把 "ask" 当作拒绝。
PostModelSwitch 在会话的模型变化之后运行:用于给 Claude 提供模型特定的指导而不必编辑每个 CLAUDE.md(如只对某些模型生效的组织级指令)。不能阻止,因为模型已变。它在这些变化之后运行:你或客户端请求的切换、自动模型回退、opusplan 之类设置进入或离开计划模式、恢复会话时还原模型;回退模型链上某个模型服务一轮时不运行(那种替换只持续一轮、不改变会话模型)。matcher 规则同 PreModelSwitch。输入:与 PreModelSwitch 相同的字段,hook_event_name 为 "PostModelSwitch",且 source 多两个取值:"auto"(自动回退等 Claude Code 自己的变更,此时 requested_model 为 null)和 "resume"(恢复会话,此时 requested_model 是还原的已保存模型设置)。输出:退出 0 的纯文本 stdout 或 JSON 的 additionalContext,随切换后的下一次请求交付给 Claude;若你发送下一个提示后 Hook 五秒内还没完成,该请求先不带输出发送,输出附到再下一次请求上。
SessionEnd
会话结束时运行:用于清理、记录会话统计或保存会话状态;matcher 按退出原因过滤。reason:clear(/clear)、resume(交互 /resume 切换会话)、logout、prompt_input_exit(提示输入可见时退出)、other;bypass_permissions_disabled 已在 v2.1.234 移除,Claude Code 不再发送,请从 SessionEnd matcher 里删掉。输入:reason。无决策控制,不能阻止会话终止;JSON 输出字段(如 systemMessage)被丢弃。默认超时只有 1.5 秒,在你退出、运行 /clear 或用交互 /resume 切换会话时适用;给 Hook 更多时间有两种方式:每个 Hook 的 timeout(总预算自动上调到你设置文件里最高的单个 timeout,上限 60 秒);或环境变量 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS(毫秒,显式覆盖预算,你设的值同时成为没有自己 timeout 的 Hook 的超时;v2.1.268 之前它只抬高总预算,没有自己 timeout 的 Hook 仍在 1.5 秒后被取消)。
Elicitation 与 ElicitationResult
Elicitation:MCP 服务器在任务中途请求用户输入时运行。默认 Claude Code 显示交互对话框;Hook 可以拦截这个请求并以编程方式响应,完全跳过对话框。matcher 匹配 MCP 服务器名。输入:mcp_server_name、message,可选 mode、url、elicitation_id、requested_schema(表单模式是最常见的;URL 模式用于基于浏览器的认证)。输出:返回 hookSpecificOutput,含 action(accept、decline、cancel)和 content(对象,要提交的表单字段值,仅 accept 时使用);退出 2 拒绝该 elicitation,且 stderr 不会显示在任何地方;Claude Code 只采纳 hookSpecificOutput,丢弃 systemMessage 和 continue。ElicitationResult:用户响应了 MCP elicitation 之后运行,Hook 可以在响应发回 MCP 服务器之前观察、修改或阻止它。matcher 同样匹配 MCP 服务器名。输入:mcp_server_name、action,可选 mode、elicitation_id、content。输出:hookSpecificOutput 的 action(覆盖用户的动作)和 content(覆盖表单字段值,仅 accept 时有意义);退出 2 阻止响应,使有效动作变为 decline,stderr 同样不显示。