Skip to content

fix: quote plugin paths in shell hook commands for Windows compatibility#30024

Draft
asafkorem wants to merge 4 commits intoanthropics:mainfrom
asafkorem:fix/windows-hook-path-backslash
Draft

fix: quote plugin paths in shell hook commands for Windows compatibility#30024
asafkorem wants to merge 4 commits intoanthropics:mainfrom
asafkorem:fix/windows-hook-path-backslash

Conversation

@asafkorem
Copy link
Copy Markdown

@asafkorem asafkorem commented Mar 2, 2026

Summary

  • Fixes shell hook commands failing on Windows due to unquoted backslash paths in ${CLAUDE_PLUGIN_ROOT} substitution
  • Affects: ralph-wiggum, learning-output-style, explanatory-output-style plugins
  • Wraps ${CLAUDE_PLUGIN_ROOT} in double quotes and adds explicit bash prefix to prevent backslash interpretation

Problem

On Windows, ${CLAUDE_PLUGIN_ROOT} resolves to a path with backslashes (e.g., C:\Users\asafk\.claude\plugins\cache\...). When the CLI executes hook commands via cmd.exe → bash (with shell: true), unquoted backslashes are interpreted by bash as escape characters:

# What bash receives (unquoted):
bash C:\Users\asafk\.claude\plugins\cache\...\stop-hook.sh

# What bash interprets (\U→U, \a→a, \.→., etc.):
C:Usersasafk.claudepluginscache...stop-hook.sh
→ "No such file or directory"

Root cause

In the CLI's hook execution function (rZ6 in cli.js), ${CLAUDE_PLUGIN_ROOT} is replaced with the raw OS path, then the resulting command string is passed to bash without quoting:

// Template substitution with raw Windows path (backslashes)
if ($) X = X.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, $);

// Windows .sh detection adds bash prefix
if (eA() === "windows" && X.trim().match(/\.sh(\s|$|")/)) {
  if (!X.trim().startsWith("bash ")) X = `bash ${X}`;
}

// Executed with shell: true (cmd.exe on Windows)
let P = fQY(j, [], { env: M, shell: true, windowsHide: true });

Fix

Wrap the ${CLAUDE_PLUGIN_ROOT} path in double quotes within hook commands. In bash double quotes, backslashes are only special before $, `, ", \, and newline — so \U, \a, \., \p, \c, \r, \1 are all preserved literally.

- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh\""

The explicit bash prefix:

  • Prevents the CLI from double-prepending bash on Windows (it skips when command already starts with bash )
  • Ensures consistent cross-platform behavior (equivalent to the #!/bin/bash shebang on Unix)

Verification

Tested on Windows 11 with Git Bash:

WIN_PATH='C:\Users\asafk\Development\claude-code\plugins\ralph-wiggum\hooks'

# BEFORE (unquoted — bug):
$ bash -c "bash ${WIN_PATH}\stop-hook.sh"
bash: C:UsersasafkDevelopmentclaude-codepluginsralph-wiggumhooksstop-hook.sh: No such file or directory

# AFTER (quoted — fix):
$ bash -c "bash \"${WIN_PATH}\stop-hook.sh\""
(exit code: 0)  # Script runs successfully

Also verified Unix-style forward-slash paths are unaffected.

Test plan

  • Reproduced original error on Windows 11 with Git Bash
  • Verified fix: quoted path preserves backslashes in bash double quotes
  • Verified no regression: Unix forward-slash paths work unchanged
  • Validated all 3 modified JSON files parse correctly
  • Manual test: install ralph-wiggum plugin on Windows and run /ralph-loop
  • Manual test: install learning-output-style plugin on Windows
  • Manual test: install explanatory-output-style plugin on Windows

🤖 Generated with Claude Code

asafkorem and others added 3 commits March 2, 2026 10:33
On Windows, `${CLAUDE_PLUGIN_ROOT}` resolves to a path with backslashes
(e.g., `C:\Users\...`). When the CLI executes hook commands via
`cmd.exe → bash`, unquoted backslashes are interpreted by bash as escape
characters, mangling the path (e.g., `C:\Users` becomes `C:Users`).

Wrapping the substituted path in double quotes preserves backslashes in
bash (only `\$`, `\"`, `\`, `` \` ``, and `\newline` are special in
double-quoted strings). Adding an explicit `bash` prefix ensures
consistent execution across platforms and prevents the CLI's Windows
`.sh` detection from double-prepending.

Affected plugins: ralph-wiggum, learning-output-style,
explanatory-output-style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…amples

Update all shell script path examples in the plugin-dev skill
documentation to use proper double-quoting around ${CLAUDE_PLUGIN_ROOT}.
This ensures plugins generated using these examples as guidance will
work correctly on Windows, where the substituted path contains
backslashes that bash interprets as escape characters when unquoted.

Affected documentation:
- hook-development: SKILL.md, patterns.md, advanced.md, migration.md
- plugin-structure: SKILL.md, manifest-reference.md, component-patterns,
  standard-plugin.md, advanced-plugin.md
- command-development: SKILL.md, plugin-commands.md,
  plugin-features-reference.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aths

- Add explicit `bash` prefix to ralph-loop.md setup script execution
  (the Bash tool path doesn't have the CLI's automatic .sh detection)
- Quote remaining unquoted ${CLAUDE_PLUGIN_ROOT} paths in test commands
  across plugin-dev documentation examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@asafkorem asafkorem marked this pull request as draft March 2, 2026 10:00
The previous approach (double-quoting ${CLAUDE_PLUGIN_ROOT}) preserved
backslashes but bash still couldn't resolve Windows paths as script
filenames. The actual fix needs to convert backslashes to forward
slashes at runtime.

New approach uses bash -c with parameter expansion:
  bash -c 'bash "${CLAUDE_PLUGIN_ROOT//\//}/hooks/stop-hook.sh"'

How it works:
- ${CLAUDE_PLUGIN_ROOT//\//} is NOT matched by the CLI's pB1 regex
  (which only matches ${CLAUDE_PLUGIN_ROOT} exactly)
- CLAUDE_PLUGIN_ROOT env var IS set by the CLI for hook execution
- Bash expands the env var and //\// replaces all \ with /
- Result: C:/Users/.../hooks/stop-hook.sh (forward slashes)
- Works with both shell:true (cmd.exe) and shell:'bash'

Verified end-to-end via Node.js spawnSync simulating exact CLI behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant