Claude Code Hooks的原理、触发执行机制以及如何编写 Hooks

Claude Code Hooks的原理、触发执行机制以及如何编写 Hooks https://code.claude.com/docs/zh-CN/hooks-guideHooks介绍1. Hooks 的原理是什么Hooks 是用户定义的自动化脚本或逻辑可以是 Shell 命令、HTTP 端点、LLM 提示或子代理它们嵌入在 Claude Code 的生命周期中。核心机制当 Claude Code 运行到生命周期的特定节点如会话开始、工具调用前、任务完成时时会检查配置文件中是否定义了匹配的 Hooks。数据交互输入Claude Code 将当前事件的上下文如工具名称、命令内容、文件路径等序列化为JSON数据。对于命令型 HooksJSON 通过stdin传递。对于 HTTP 型 HooksJSON 作为POST请求体发送。输出Hook 处理程序执行后返回结果给 Claude Code。控制流通过退出代码Exit Code或返回特定的 JSON 字段如permissionDecision: deny或decision: block来告诉 Claude Code 是允许继续、阻止操作还是修改输入。目的用于实现安全验证如禁止rm -rf、自动 linting、环境设置、审计日志记录或自定义工作流拦截。2. 如何触发和执行Hooks 的触发和执行遵循以下流程A. 触发时机 (Lifecycle Events)Hooks 在特定的生命周期事件发生时触发。常见的事件包括SessionStart: 会话开始时。UserPromptSubmit: 用户提交提示后Claude 处理前。PreToolUse:最常用在工具如 Bash, Write, Read执行之前。可用于拦截和修改工具调用。PostToolUse: 工具成功执行后。Stop: Claude 准备结束回答时。其他PermissionRequest,SubagentStart,TaskCompleted等。B. 匹配机制 (Matching)当事件触发时系统会检查配置文件中的matcher匹配器如果事件支持匹配器如PreToolUse它会检查工具名称如Bash,Write是否匹配正则表达式。如果不匹配Hook 被跳过如果匹配或未定义匹配器则执行 Hook。C. 执行流程事件发生例如Claude 决定运行Bash: rm -rf /tmp。数据发送Claude 将包含tool_name: Bash和tool_input: { command: rm -rf /tmp }的 JSON 发送给 Hook。Hook 运行Command Hook: 运行本地 Shell 脚本。HTTP Hook: 发送 HTTP POST 请求到指定 URL。Prompt/Agent Hook: 调用 LLM 或生成子代理进行判断。结果处理允许脚本退出码为0且无阻止指令 - Claude 继续执行原操作。阻止脚本退出码为2或返回 JSON{ permissionDecision: deny }- Claude 取消操作并报错。修改返回 JSON 包含updatedInput- Claude 使用修改后的参数执行。3. 如何写一个 Hooks?编写 Hooks 主要分为两步配置定义和实现逻辑。第一步配置文件 (settings.json)在项目根目录的.claude/settings.json或用户全局配置~/.claude/settings.json中定义。基本结构示例拦截危险的 Bash 命令{hooks:{PreToolUse:[{matcher:Bash,hooks:[{type:command,command:.claude/hooks/block-rm.sh}]}]}}PreToolUse: 事件类型。matcher: 过滤条件这里只针对Bash工具。type: 可以是command(Shell),http,prompt,agent。command: 要执行的脚本路径。第二步实现脚本逻辑 (以 Command Hook 为例)创建一个 Shell 脚本如.claude/hooks/block-rm.sh使其可执行 (chmod x)。脚本逻辑模板读取输入从stdin读取 JSON。解析数据提取需要的信息如命令字符串。判断逻辑根据业务规则判断是否允许。返回结果允许直接exit 0。阻止打印错误信息到stderr并exit 2或者打印特定 JSON 到stdout并exit 0。示例脚本内容 (block-rm.sh)#!/bin/bash# 从 stdin 读取 JSON 输入INPUT$(cat)# 提取命令内容COMMAND$(echo$INPUT|jq-r.tool_input.command)# 检查是否包含危险命令ifecho$COMMAND|grep-qrm -rf;then# 方法 A: 使用退出码 2 阻止 (简单粗暴)echoBlocked: Destructive command detected!2exit2# 方法 B: 使用 JSON 输出阻止 (更灵活可定制理由)# jq -n {# hookSpecificOutput: {# hookEventName: PreToolUse,# permissionDecision: deny,# permissionDecisionReason: Destructive command blocked by hook# }# }# exit 0else# 允许执行exit0fi高级用法提示异步执行在配置中添加async: true让 Hook 在后台运行适用于耗时任务如运行测试套件不会阻塞 Claude。基于 LLM 的判断设置type: prompt让 Claude 自己判断是否允许某个操作例如“检查这个文件修改是否符合代码规范”。环境变量脚本中可以使用$CLAUDE_PROJECT_DIR来获取项目根目录确保路径正确。通过以上步骤您就可以根据需求定制 Claude Code 的行为增强安全性或自动化工作流。Hook 加载机制Claude Code 有两种 hook 配置方式1. 插件 Hook推荐方式在插件目录下的hooks/hooks.json中定义插件目录/ └── hooks/ └── hooks.json ← Claude Code 启动时自动扫描发现加载流程Claude Code 启动时扫描所有已安装插件检查每个插件是否存在hooks/hooks.json将发现的 hooks 加载到当前会话插件必须被启用才能生效2. 用户级 Hook直接在~/.claude/settings.json中配置{PreToolUse:[...],Stop:[...]}特点无需包裹层直接在顶层定义事件无description字段立即生效下次会话大模型如何知道有 hooks不是大模型知道是 Claude Code 运行时框架在拦截事件时执行 hook 逻辑用户发送消息 ↓ Claude Code 拦截 [UserPromptSubmit hook 触发] ↓ 执行 hook 配置的逻辑prompt 或 command ↓ 根据 hook 输出决定如何处理 ↓ 大模型收到的是经过 hook 处理后的上下文Hook 在框架层面执行模型本身不知道有 hooks 存在。生效条件条件说明插件已启用在settings.json的plugins列表中会话启动Hook 在会话开始时加载重启生效修改 hooks 后需重启 Claude Code配置位置对比方式配置文件格式插件 Hook插件/hooks/hooks.json{hooks: {...}}用户 Hook~/.claude/settings.json直接顶层配置常见误区❌ 不是用户手动定义的❌ 不是写在某处配置文件的✅ 由 Claude Code 运行时自动注入到 command hooks可用场景在所有command hooks中可直接使用{type:command,command:bash $CLAUDE_PLUGIN_ROOT/scripts/validate.sh}实际值示例假设你正在使用everything-claude-code插件CLAUDE_PLUGIN_ROOT /Users/username/.claude/plugins/marketplaces/everything-claude-code不同插件的 hook 运行时CLAUDE_PLUGIN_ROOT会指向各自插件目录。相关的环境变量变量说明$CLAUDE_PROJECT_DIR项目根目录路径$CLAUDE_PLUGIN_ROOT插件目录路径$CLAUDE_ENV_FILESessionStart 专用持久化环境变量的文件$CLAUDE_CODE_REMOTE是否运行在远程上下文为什么使用它可移植性使用$CLAUDE_PLUGIN_ROOT而不是硬编码绝对路径让 hook 脚本能在不同环境下复用。# ✅ 好可移植bash$CLAUDE_PLUGIN_ROOT/scripts/validate.sh# ❌ 差硬编码路径bash/Users/username/.claude/plugins/cache/claude-plugins-official/plugin-dev/55b58ec6e564/scripts/validate.sh