Claude Code hook 在 Windows 上不起作用:原因与解决方法
同一个 hook 在 Mac 上运行正常,到了 Windows 上却毫无反应,甚至更糟,拦下所有命令。罪魁祸首几乎总是 shell。下面介绍 Claude Code 在 Windows 上如何运行 hook,以及如何让你的 hook 稳定可靠。
简短回答
在 Windows 上,Claude Code 使用 Git Bash 运行 hook 的 command;如果没有安装 Git Bash,则使用 PowerShell。用 "shell": "powershell" 或 "shell": "bash" 明确指定要用哪一个,让 stdout 只输出一个 JSON 对象(不要有配置文件的 echo 输出),用退出码 2 来拦截,并更新 Claude Code(截至 2.1.310 的多个版本修复了若干 Windows hook 缺陷)。使用 /hooks 和 claude --debug 进行调试。
由哪个 shell 运行你的 hook
Claude Code 会把 command 字符串交给一个 shell:macOS 和 Linux 上是 sh -c,Windows 上是 Git Bash,没有安装 Git Bash 时则是 PowerShell。hook 参考文档中的这一句话,就解释了大多数“在我的 Mac 上没问题”的反馈。为 PowerShell 编写的命令在 Git Bash 中会失败,bash 脚本在 PowerShell 中会失败,而你实际用的是哪一个,取决于电脑上恰好有没有安装 Git for Windows。
用 shell 字段消除不确定性。它接受 "bash" 或 "powershell":
如果你想用 Git Bash 而 Claude Code 找不到它,请安装 Git for Windows,或将 CLAUDE_CODE_GIT_BASH_PATH 设为其所在位置,然后开启新会话。
症状及其原因
| 你看到的现象 | 可能的原因 | 解决方法 |
|---|---|---|
| hook 似乎从未运行 | Claude Code 版本过旧、事件或 matcher 写错、设置中的 JSON 无效 | 更新;检查 /hooks;删除注释和末尾逗号 |
ParserError 或“unexpected token” | PowerShell 语法被交给了 Git Bash,或者反过来 | 显式设置 shell |
| hook 输出“does not start with {”(不是以 { 开头) | JSON 之前打印了其他内容,常见来源是 shell 配置文件 | 屏蔽配置文件的输出;把日志发送到 stderr |
混用 \ 和 / 的路径找不到 | Windows 路径变量与 Unix 风格的后缀拼接在一起 | 使用 exec 形式:程序写在 command 中,每个参数写在 args 中 |
Git Bash 报出 powershell: Permission denied | 在 2.1.283 和 2.1.304 至 2.1.305 中有间歇性报告(#103910,尚未关闭) | 使用 "shell": "powershell",让 Git Bash 不再夹在中间 |
| 防护 hook 放行了所有操作 | 退出码用了 1 而不是 2,或旧版本中存在失败即放行(fail-open)的缺陷 | 用退出码 2 拦截;更新到 2.1.310 或更高版本 |
| hook 立即返回且没有输出 | GUI 子系统程序,shell 不会等待它结束 | 改用控制台程序,或等待该进程结束 |
stdout 中只输出 JSON
Claude Code 会把 hook 的标准输出当作它的答复。文档中的规则很简单:除了 JSON 对象,不要向 stdout 打印任何内容。在 Windows 上,这一点会以一种隐蔽的方式出问题:Git Bash 和某些环境仍会加载你的 shell 配置文件,而配置文件中无条件执行的 echo 会被加在你的 JSON 前面。于是 hook 在终端里“能用”,在 Claude Code 中却失败。
请改为把日志写到文件或 stderr,并让配置文件只在交互式 shell 中才产生输出。
退出码:2 会拦截,1 不会
退出码 0 表示成功。退出码 2 表示阻塞性错误:对于可以拦截的事件,Claude Code 会中止该操作并显示你的 stderr。退出码 1 且没有有效 JSON 时属于非阻塞性错误,操作会继续执行。 如果你的 hook 用于执行策略,请使用退出码 2。
并非所有事件都能用这种方式拦截。Notification hook 的退出码会被忽略。Stop hook 以 2 退出会让 Claude 继续工作。PermissionRequest hook 完全忽略退出码 2,它通过 JSON 中的 decision 对象来做决定。
Claude Code 2.1.309 修复了 hook 程序以退出码 2 退出时 PowerShell hook 无法拦截的问题。如果你的 PowerShell 防护似乎被忽略了,请先检查版本。
值得为之更新的 Windows 修复
Anthropic 的更新日志中有一长串 Windows hook 修复。以下几项可以解释论坛上的旧帖:
| 版本 | 修复内容 |
|---|---|
| 2.1.47 | PreToolUse 和 PostToolUse hook 在 Windows 上静默失败;现在改用 Git Bash 而不是 cmd.exe 运行 |
| 2.1.161 | 显式调用 bash 的 hook(/usr/bin/bash script.sh)报错“command not found” |
| 2.1.299 | 未设置 shell 的 PreToolUse 和 PermissionRequest hook 在没有 Git for Windows 时会失败放行;现在失败的防护会执行拦截 |
| 2.1.309 | PowerShell hook 遇到退出码 2 时不拦截;在没有 Git for Windows 时,"shell": "powershell" 防护会失败放行 |
| 2.1.310 | 命令仅由带引号的字符串(例如带引号的路径)构成的防护,在没有 Git Bash 时会失败放行 |
较早的那个标题为“No hook is working on Windows”的 issue(#10450)是在 2.0.27 上报告的;一位评论者查明原因是 stdin 没有传到 hook 命令。如果你的版本这么旧,请先更新再调试其他问题。
直接调用程序
如果你的 hook 是一个编译好的程序,可以用 exec 形式跳过 shell(即 args 字段;设置了 args 时 shell 会被忽略)。在 Windows 上,exec 形式要求 command 解析为真正的可执行文件,例如 .exe;.cmd 和 .bat 包装脚本(包括 npm 安装的大多数东西)不能在没有 shell 的情况下启动。请指向 .exe,或者使用 shell 形式。
我们在开发自己的 hook 辅助程序时得到一条教训:在 Windows 上,shell 启动为 GUI 子系统编译的程序时不会等待它结束。shell 会立即返回,智能体读到的是空输出,hook 看上去毫无反应。hook 程序必须是控制台程序。
插件 hook 与路径
在 v2.1.2 到 v2.1.12 之间,拼接 ${CLAUDE_PLUGIN_ROOT}/hooks/run.sh 这类路径的插件 hook 在 Windows 上无法运行(#18610):该变量展开后是反斜杠路径,再加上正斜杠后缀,就成了 bash 无法解析的混合路径。当前文档给出了干净的解决办法:只要 hook 引用路径占位符,就使用 exec 形式。把程序放在 command 中,每个参数放在 args 中,例如 "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js"]。每个元素都作为一个独立参数传递,不会被 shell 篡改。
调试清单
/hooks会打开一个只读列表,列出所有已配置的 hook 及其来源。claude --debug(或在会话中使用/debug)会显示每次 hook 运行、输出和退出码。claude --debug-file <path>会把这些内容写入文件。- 验证 JSON。 设置文件是严格的 JSON;一个末尾逗号就会让整个文件失效。
- 检查 matcher。 matcher 区分大小写;
"*"、""或不写 matcher 都会匹配所有内容。 - 自己运行一遍命令,使用 Claude Code 将使用的同一个 shell,并通过管道传入示例 JSON。
- 检查超时。 命令 hook 的默认超时为 600 秒;需要等人响应的 hook 需要这么长的余量。
排查期间如需关闭所有 hook,请设置 "disableAllHooks": true。
如果你只是想要通知和审批
很多 Windows hook 配置只为一个目的:知道 Claude Code 什么时候需要你,并做出回应。Pet Island 无需任何脚本就能做到这一点。它一键连接 Claude Code,替你处理好 Windows 上的细节,并把每个权限请求、提问和计划连同命令或 diff 一起放到屏幕顶部的小岛上,你在那里允许或拒绝即可。Claude Code 在 WSL 中运行时它也能工作。
常见问题
Windows 上由哪个 shell 运行 Claude Code hook?
Git Bash;如果没有安装 Git Bash,则是 PowerShell。在 hook 中设置 "shell": "powershell" 或 "shell": "bash" 可以明确指定。
为什么我的 Claude Code hook 在终端中可以运行,在 Claude Code 中却不行?
通常是多余的输出:会 echo 文本的 shell 配置文件把内容加在了 hook 的 JSON 前面。stdout 中只打印 JSON 对象。
如何让 hook 拦截工具调用?
以退出码 2 退出(或在 JSON 中返回 deny 决定)。退出码 1 是非阻塞性错误,操作会继续执行。PermissionRequest hook 会忽略退出码 2,改用 decision 对象。
hook 命令可以是 .cmd 或 .bat 文件吗?
exec 形式下不行。在 Windows 上,它必须解析为真正的可执行文件,例如 .exe。要运行 .cmd 或 .bat 文件,请使用 shell 形式。
如何查看 hook 失败的原因?
运行 claude --debug 或输入 /debug,并用 /hooks 查看加载了哪些 hook。
参考来源
本页的每项设置和行为都已于 2026年10月26日 对照以下页面核实。智能体更新很快;如有不符,以官方页面为准。
- Claude Code 文档:Hooks reference
- Claude Code 文档:Hooks guide
- Claude Code:CHANGELOG
- Issue #10450:Windows 上所有 hook 都不起作用
- Issue #18610:插件 hook 无法在 Windows 上执行脚本
- Issue #103910:hook 间歇性无法启动 powershell(Git Bash)