Skip to content

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,这带来三个不兼容问题:

  1. 脚本执行:CMD 无法直接执行 .sh 文件,会尝试用关联程序打开(通常是记事本)
  2. 路径格式:Windows 路径用反斜杠(C:\path),Unix 路径用正斜杠(/path
  3. 环境变量$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.execygpath。默认安装路径是 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.execygpath。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,需要另外处理。