Appearance
Superpowers 的 hooks 在 Windows 上踩坑的根源很简单:Claude Code 在 Windows 通过 CMD.exe 执行 hook 命令,而 Superpowers 的 hook 逻辑写在 .sh 文件里,CMD 根本不会执行它——只会用文本编辑器把它打开。解决方案是一个 polyglot .cmd 包装器:同一个文件在 CMD 里当批处理脚本执行,在 bash 里当 shell 脚本执行,分别走不同的代码路径。
Windows 上使用 Superpowers:polyglot hooks 配置指南
为什么 Windows 上 hooks 不工作
在 macOS 和 Linux 上,Claude Code 用 bash 或 sh 执行 hook 命令,一切正常。在 Windows 上,默认 shell 是 CMD.exe,这带来三个不兼容问题:
- 脚本执行:CMD 无法直接执行
.sh文件,会尝试用关联程序打开(通常是记事本) - 路径格式:Windows 路径用反斜杠(
C:\path),Unix 路径用正斜杠(/path) - 环境变量:
$VAR语法在 CMD 里不工作,需要用%VAR%
最终现象:配置好的 hooks 完全没有反应,或者弹出文本编辑器窗口。
核心解决方案:Polyglot .cmd 包装器
Polyglot 脚本是同时在多种语言中有效的代码。下面这个文件在 CMD 和 bash 里都能运行,但走不同的路径:
cmd
: << 'CMDBLOCK'
@echo off
"C:\Program Files\Git\bin\bash.exe" -l -c "\"$(cygpath -u \"$CLAUDE_PLUGIN_ROOT\")/hooks/session-start.sh\""
exit /b
CMDBLOCK
# Unix shell runs from here
"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"CMD 里发生了什么
- 第一行
: << 'CMDBLOCK'— CMD 把:解析为标签,忽略后面的内容 @echo off— 关闭命令回显- 用完整路径调用
bash.exe,加-l(login shell)确保 PATH 完整 cygpath -u将 Windows 路径转换为 Unix 格式(C:\foo→/c/foo)exit /b— CMD 在这里退出,后面的内容永远不会被 CMD 执行
bash 里发生了什么
: << 'CMDBLOCK'—:是 bash 的 no-op(空操作),<< 'CMDBLOCK'开启 heredoc- 从这行到
CMDBLOCK之间的所有内容被 heredoc 消费掉(忽略) - heredoc 结束后,直接执行
.sh文件
一份文件,两套逻辑,互不干扰。
文件结构
hooks/
├── hooks.json # 指向 .cmd 包装器
├── session-start.cmd # Polyglot 包装器(跨平台入口)
└── session-start.sh # 实际 hook 逻辑(bash 脚本)hooks.json 里的关键点——路径必须加引号,因为 ${CLAUDE_PLUGIN_ROOT} 在 Windows 上可能包含空格(比如 C:\Program Files\...):
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.cmd\""
}
]
}
]
}
}前置要求
Windows 端:必须安装 Git for Windows,它提供了 bash.exe 和 cygpath。默认安装路径是 C:\Program Files\Git\bin\bash.exe。如果你装在其他目录,需要相应修改包装器里的路径。
Unix 端(macOS/Linux):.cmd 文件需要执行权限,用 chmod +x 设置即可。
多 Hook 场景:通用包装器
如果你有多个 hook,每个都写一遍包装器很麻烦。可以用一个通用包装器 run-hook.cmd 接收脚本名作为参数:
cmd
: << 'CMDBLOCK'
@echo off
set "SCRIPT_DIR=%~dp0"
set "SCRIPT_NAME=%~1"
"C:\Program Files\Git\bin\bash.exe" -l -c "cd \"$(cygpath -u \"%SCRIPT_DIR%\")\" && \"./%SCRIPT_NAME%\""
exit /b
CMDBLOCK
# Unix shell runs from here
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT_NAME="$1"
shift
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"然后 hooks.json 里这样引用:
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{"type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh"}]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" validate-bash.sh"}]
}
]
}
}编写跨平台 Hook 逻辑(.sh 文件)
实际的 hook 逻辑写在 .sh 文件里,通过 Git Bash 在 Windows 上执行。有几个注意点:
该用的:
- 纯 bash built-in,减少对外部命令的依赖
$(command)替代反引号- 变量展开加引号:
"$VAR" - 用
printf或 here-doc 输出内容
该避免的:
- 不加
-l时 sed、awk、grep 等命令可能不在 PATH 里 - Windows 路径直接拼接(用
cygpath转换)
如果必须做 JSON 转义这类操作,优先用纯 bash 实现,不依赖 sed:
bash
escape_for_json() {
local input="$1"
local output=""
local i char
for (( i=0; i<${#input}; i++ )); do
char="${input:$i:1}"
case "$char" in
$'\\') output+='\\' ;;
'"') output+='\"' ;;
$'\n') output+='\n' ;;
$'\t') output+='\t' ;;
*) output+="$char" ;;
esac
done
printf '%s' "$output"
}常见报错排查
"bash is not recognized as an internal or external command"
CMD 找不到 bash。包装器里硬编码了 C:\Program Files\Git\bin\bash.exe,如果 Git 装在其他位置,改这里的路径。
"cygpath: command not found" 或 "dirname: command not found"
bash 没有以 login shell 模式运行。确认 -l 参数存在。
路径里出现奇怪的 \/ 组合
${CLAUDE_PLUGIN_ROOT} 展开后是 Windows 路径且以反斜杠结尾,然后 /hooks/... 被拼接进去。用 cygpath 转换整个路径。
脚本用文本编辑器打开
hooks.json 里直接指向了 .sh 文件。改成指向 .cmd 包装器。
在终端手动运行正常,作为 hook 就不工作
可以用下面的命令模拟 hook 执行环境来调试:
powershell
$env:CLAUDE_PLUGIN_ROOT = "C:\path\to\plugin"
cmd /c "C:\path\to\plugin\hooks\session-start.cmd"FAQ
Q: 必须用 Git for Windows 吗?能用 WSL 吗? A: 目前这套方案依赖 Git for Windows 提供的 bash.exe 和 cygpath。WSL 也可以实现类似目标,但需要不同的包装器写法——主要是路径转换逻辑不同(WSL 用 wslpath 而不是 cygpath)。
Q: 我的 Git 装在自定义路径,有没有办法不写死路径? A: 可以用 where git 命令找到 git 位置,再推算 bash.exe 路径。但这会让包装器复杂很多。更简单的做法是把 Git Bash 加入 PATH,然后直接调用 bash,但这要求 Git Bash 在所有用到的 CMD 会话里都在 PATH 里。
Q: .cmd 文件需要设置 Unix 执行权限吗? A: 在 Windows 上不需要。在 macOS/Linux 上,需要 chmod +x,否则 bash 无法直接执行它。
Q: 这个方案对 PowerShell 也有效吗? A: Claude Code 在 Windows 上走 CMD,不是 PowerShell,所以这套方案针对 CMD。如果你的环境配置成用 PowerShell 执行 hook,需要另外处理。