Skip to content

fix(hooks): fix Windows hooks for Claude Code 2.1.x#398

Closed
LeekJay wants to merge 1 commit intoobra:mainfrom
LeekJay:fix/windows-hooks-claude-code-2.1.x
Closed

fix(hooks): fix Windows hooks for Claude Code 2.1.x#398
LeekJay wants to merge 1 commit intoobra:mainfrom
LeekJay:fix/windows-hooks-claude-code-2.1.x

Conversation

@LeekJay
Copy link

@LeekJay LeekJay commented Feb 1, 2026

Problem

Claude Code 2.1.x changed the Windows execution model for hooks. It now auto-detects .sh files in hook commands and prepends bash on Windows.

However, there are two issues:

Issue 1: Polyglot wrapper broken by auto-detection

When the command contains .sh suffix:

"run-hook.cmd" session-start.sh
→ bash "run-hook.cmd" session-start.sh
→ bash cannot execute .cmd files

Issue 2: CLAUDE_PLUGIN_ROOT not normalized on Windows

Claude Code does not normalize ${CLAUDE_PLUGIN_ROOT} to Unix-style paths on Windows. The variable expands to a Windows path with backslashes:

${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh
→ C:\Users\...\superpowers\4.1.1/hooks/session-start.sh

This mixed path format (Windows backslashes + Unix forward slashes) causes bash to fail:

/bin/bash: C:\Users\...\superpowers\4.1.1/hooks/session-start.sh: No such file or directory

So even calling .sh directly doesn't work on Windows.

Solution

Use the polyglot wrapper run-hook.cmd which:

  1. Handles path conversion using cygpath -u to convert Windows paths to Unix format
  2. Accepts script names WITHOUT .sh suffix to avoid Claude Code's auto-detection
  3. Auto-appends .sh suffix internally
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start"

Changes

  • hooks/hooks.json: Use run-hook.cmd wrapper with script name without .sh suffix
  • hooks/run-hook.cmd: Update comments to reflect current usage; auto-append .sh suffix

Testing

Tested on Windows 11 with Claude Code 2.1.25.

Summary by CodeRabbit

  • Chores
    • Updated hook invocation wrapper to improve cross-platform behavior between Windows and Unix, including clearer invocation rules and suffix handling.
    • Adjusted SessionStart hook configuration to use the updated wrapper for more consistent execution across environments.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Warning

Rate limit exceeded

@LeekJay has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Replaced the SessionStart hook invocation with a Windows CMD wrapper (hooks/run-hook.cmd) and updated the wrapper to append a .sh suffix, select Bash (CLAUDE_BASH_EXE or Git Bash) on Windows, handle MSYS2 arg conversion, and call the corresponding hooks/*.sh script; Unix branch also appends .sh.

Changes

Cohort / File(s) Summary
Hook Configuration
hooks/hooks.json
Changed SessionStart command from a direct Unix script path to a CMD invocation: hooks/run-hook.cmd session-start with adjusted quoting for Windows.
Hook Invocation Wrapper
hooks/run-hook.cmd
Rewrote the wrapper into a cross-platform polyglot: mandates script names without .sh, auto-appends .sh, chooses CLAUDE_BASH_EXE or Git Bash on Windows, excludes MSYS2 arg conversion, converts paths (cygpath) and invokes bash -l -c; Unix branch now guards for missing script name and appends .sh when invoking.

Sequence Diagram(s)

sequenceDiagram
    participant CMD as run-hook.cmd (Windows CMD)
    participant Shell as CLAUDE_BASH_EXE / Git Bash
    participant Script as hooks/<script>.sh

    CMD->>CMD: parse SCRIPT_NAME and shift args (exclude MSYS2 conversion)
    CMD->>Shell: convert paths (cygpath) and call "SCRIPT_DIR/SCRIPT_NAME.sh" with args
    Shell->>Script: execute "/path/to/SCRIPT_NAME.sh" (bash -l -c)
    Script-->>Shell: exit status / output
    Shell-->>CMD: propagate exit status / output
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

Poem

🐇
I hopped on paths both backslash and slash,
A tiny CMD wrapper fixed the clash.
I tuck a .sh in every hat I wear,
And nudge Bash gently to run with care. 🎩

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(hooks): fix Windows hooks for Claude Code 2.1.x' accurately and specifically describes the main change: fixing Windows hook compatibility issues for Claude Code 2.1.x. It directly relates to the primary objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hooks/run-hook.cmd (1)

4-24: ⚠️ Potential issue | 🟡 Minor

Misleading deprecation comments contradict actual usage.

The header states the wrapper is "no longer used" and that "hooks.json now calls session-start.sh directly" (line 21), but hooks.json actually invokes run-hook.cmd session-start. This will confuse future maintainers.

Consider updating the comments to accurately describe the current role: the wrapper is still used, but the .sh suffix is now appended internally to avoid Claude Code's auto-detection.

@LeekJay LeekJay force-pushed the fix/windows-hooks-claude-code-2.1.x branch 2 times, most recently from 8635647 to 2cc7d89 Compare February 1, 2026 10:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@hooks/run-hook.cmd`:
- Around line 39-43: Add a guard that checks whether SCRIPT_NAME (the first
positional argument) is empty before attempting to execute
"${SCRIPT_DIR}/${SCRIPT_NAME}.sh": if SCRIPT_NAME is unset or empty, print a
clear error message and exit with non-zero status (mirror the existing
Windows/cmd branch behavior) so the script does not try to run
"${SCRIPT_DIR}/.sh"; update the logic around SCRIPT_NAME and SCRIPT_DIR to
perform this check and early exit.
- Around line 33-34: The current invocation of bash forwards only %~2–%9
unquoted which truncates args, breaks spaces and enables injection; change the
logic around the "%~1" script dispatch to SHIFT once and invoke bash with the
full remaining argv forwarded safely using -- %* (so the script name comes from
%~1 and the rest become "$@" inside bash), ensure arguments are properly quoted
when passed to bash, set MSYS2_ARG_CONV_EXCL='*' in the environment before
calling bash to avoid MSYS path conversion, and make the bash path configurable
(e.g., support %CLAUDE_BASH_EXE% or resolve bash from PATH) instead of
hardcoding "C:\Program Files\Git\bin\bash.exe".

@LeekJay LeekJay force-pushed the fix/windows-hooks-claude-code-2.1.x branch from 2cc7d89 to f5803d7 Compare February 1, 2026 10:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@hooks/run-hook.cmd`:
- Around line 33-43: The batch wrapper leaves the original first argument
present in %* which ends up as $1 inside the invoked bash command, so update the
bash invocation lines (the -c argument used in the CLAUDE_BASH_EXE branch and
the default Git Bash branch) to run a shell-side shift before sourcing the hook;
specifically, modify the command passed to bash in the calls that reference
"%CLAUDE_PLUGIN_ROOT%"/hooks/%SCRIPT_NAME%.sh so the inline -c script begins
with shift; and then executes the hook with "$@" so the hook's $1 is the first
original user arg rather than the script name.

Claude Code 2.1.x auto-detects .sh files in hook commands and prepends
"bash " on Windows. This breaks the polyglot wrapper because:

  "run-hook.cmd" session-start.sh
  → bash "run-hook.cmd" session-start.sh
  → bash cannot execute .cmd files

Solution:
- Remove .sh suffix from hook command arguments
- Auto-append .sh suffix inside run-hook.cmd

This avoids Claude Code's .sh detection while maintaining cross-platform
compatibility.
@LeekJay LeekJay force-pushed the fix/windows-hooks-claude-code-2.1.x branch from f5803d7 to 2e6b44e Compare February 1, 2026 10:35
@obra obra added windows hooks Hook system (SessionStart, Stop, etc.) bug Something isn't working claude-code Claude Code (Anthropic CLI) issues labels Feb 5, 2026
@obra
Copy link
Owner

obra commented Feb 5, 2026

Thank you for the thorough work on this! We ended up going a different direction — removing the polyglot wrapper and calling session-start.sh directly, with async: true to prevent terminal freeze.

The core path mangling issue (CC strips backslashes from ${CLAUDE_PLUGIN_ROOT} during template substitution) affects all approaches that depend on that variable, including the wrapper path itself. We're tracking that as an upstream CC bug at #420.

@obra obra closed this Feb 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working claude-code Claude Code (Anthropic CLI) issues hooks Hook system (SessionStart, Stop, etc.) windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants