Files
SuperCharged-Claude-Code-Up…/plugins/cache/superpowers/superpowers/4.0.3/docs/windows/polyglot-hooks.md
uroma 7a491b1548 SuperCharge Claude Code v1.0.0 - Complete Customization Package
Features:
- 30+ Custom Skills (cognitive, development, UI/UX, autonomous agents)
- RalphLoop autonomous agent integration
- Multi-AI consultation (Qwen)
- Agent management system with sync capabilities
- Custom hooks for session management
- MCP servers integration
- Plugin marketplace setup
- Comprehensive installation script

Components:
- Skills: always-use-superpowers, ralph, brainstorming, ui-ux-pro-max, etc.
- Agents: 100+ agents across engineering, marketing, product, etc.
- Hooks: session-start-superpowers, qwen-consult, ralph-auto-trigger
- Commands: /brainstorm, /write-plan, /execute-plan
- MCP Servers: zai-mcp-server, web-search-prime, web-reader, zread
- Binaries: ralphloop wrapper

Installation: ./supercharge.sh
2026-01-22 15:35:55 +00:00

6.1 KiB

Cross-Platform Polyglot Hooks for Claude Code

Claude Code plugins need hooks that work on Windows, macOS, and Linux. This document explains the polyglot wrapper technique that makes this possible.

The Problem

Claude Code runs hook commands through the system's default shell:

  • Windows: CMD.exe
  • macOS/Linux: bash or sh

This creates several challenges:

  1. Script execution: Windows CMD can't execute .sh files directly - it tries to open them in a text editor
  2. Path format: Windows uses backslashes (C:\path), Unix uses forward slashes (/path)
  3. Environment variables: $VAR syntax doesn't work in CMD
  4. No bash in PATH: Even with Git Bash installed, bash isn't in the PATH when CMD runs

The Solution: Polyglot .cmd Wrapper

A polyglot script is valid syntax in multiple languages simultaneously. Our wrapper is valid in both CMD and bash:

: << '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"

How It Works

On Windows (CMD.exe)

  1. : << 'CMDBLOCK' - CMD sees : as a label (like :label) and ignores << 'CMDBLOCK'
  2. @echo off - Suppresses command echoing
  3. The bash.exe command runs with:
    • -l (login shell) to get proper PATH with Unix utilities
    • cygpath -u converts Windows path to Unix format (C:\foo/c/foo)
  4. exit /b - Exits the batch script, stopping CMD here
  5. Everything after CMDBLOCK is never reached by CMD

On Unix (bash/sh)

  1. : << 'CMDBLOCK' - : is a no-op, << 'CMDBLOCK' starts a heredoc
  2. Everything until CMDBLOCK is consumed by the heredoc (ignored)
  3. # Unix shell runs from here - Comment
  4. The script runs directly with the Unix path

File Structure

hooks/
├── hooks.json           # Points to the .cmd wrapper
├── session-start.cmd    # Polyglot wrapper (cross-platform entry point)
└── session-start.sh     # Actual hook logic (bash script)

hooks.json

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume|clear|compact",
        "hooks": [
          {
            "type": "command",
            "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.cmd\""
          }
        ]
      }
    ]
  }
}

Note: The path must be quoted because ${CLAUDE_PLUGIN_ROOT} may contain spaces on Windows (e.g., C:\Program Files\...).

Requirements

Windows

  • Git for Windows must be installed (provides bash.exe and cygpath)
  • Default installation path: C:\Program Files\Git\bin\bash.exe
  • If Git is installed elsewhere, the wrapper needs modification

Unix (macOS/Linux)

  • Standard bash or sh shell
  • The .cmd file must have execute permission (chmod +x)

Writing Cross-Platform Hook Scripts

Your actual hook logic goes in the .sh file. To ensure it works on Windows (via Git Bash):

Do:

  • Use pure bash builtins when possible
  • Use $(command) instead of backticks
  • Quote all variable expansions: "$VAR"
  • Use printf or here-docs for output

Avoid:

  • External commands that may not be in PATH (sed, awk, grep)
  • If you must use them, they're available in Git Bash but ensure PATH is set up (use bash -l)

Example: JSON Escaping Without sed/awk

Instead of:

escaped=$(echo "$content" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')

Use pure 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' ;;
            $'\r') output+='\r' ;;
            $'\t') output+='\t' ;;
            *) output+="$char" ;;
        esac
    done
    printf '%s' "$output"
}

Reusable Wrapper Pattern

For plugins with multiple hooks, you can create a generic wrapper that takes the script name as an argument:

run-hook.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 "${BASH_SOURCE[0]:-$0}")" && pwd)"
SCRIPT_NAME="$1"
shift
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"

hooks.json using the reusable wrapper

{
  "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"
          }
        ]
      }
    ]
  }
}

Troubleshooting

"bash is not recognized"

CMD can't find bash. The wrapper uses the full path C:\Program Files\Git\bin\bash.exe. If Git is installed elsewhere, update the path.

"cygpath: command not found" or "dirname: command not found"

Bash isn't running as a login shell. Ensure -l flag is used.

Path has weird \/ in it

${CLAUDE_PLUGIN_ROOT} expanded to a Windows path ending with backslash, then /hooks/... was appended. Use cygpath to convert the entire path.

Script opens in text editor instead of running

The hooks.json is pointing directly to the .sh file. Point to the .cmd wrapper instead.

Works in terminal but not as hook

Claude Code may run hooks differently. Test by simulating the hook environment:

$env:CLAUDE_PLUGIN_ROOT = "C:\path\to\plugin"
cmd /c "C:\path\to\plugin\hooks\session-start.cmd"