Skip to content

Conversation

@zeynalnia
Copy link

@zeynalnia zeynalnia commented Jan 17, 2026

Summary

  • Fixes SessionStart hook error when WSL's bash.exe is the default shell on Windows
  • Uses wslpath to convert Windows paths to WSL format, with fallback for non-WSL environments

Problem

When WSL's bash.exe is in PATH (either alone or before Git's bash.exe), Claude Code passes Windows-style paths that WSL bash cannot resolve, causing "No such file or directory" errors.

How to reproduce

  • PATH contains only WSL's bash.exe path, OR
  • PATH contains both WSL and Git bash.exe paths but WSL comes first

Fixes #275

Test plan

  • Test on Windows with WSL bash as default shell
  • Test on Windows with Git Bash only
  • Test on native Linux/macOS

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows Subsystem for Linux compatibility when running hooks by preferring a WSL-friendly path and automatically falling back to the original path if conversion fails, ensuring more reliable hook execution across environments.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Updated the SessionStart hook command to attempt converting CLAUDE_PLUGIN_ROOT to a WSL-style path with wslpath -u, then fall back to the original path if conversion fails.

Changes

Cohort / File(s) Summary
Hook Configuration Update
hooks/hooks.json
SessionStart hook command changed to `"$(wslpath -u '${CLAUDE_PLUGIN_ROOT}')/hooks/run-hook.cmd" session-start.sh

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through paths both backslash and slash,
I nudged a hook to try WSL first, then dash—fallback!
Now startup sings without a fright,
Hooks run smooth, both day and night. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: handling WSL bash as the default shell on Windows by using wslpath for path conversion.
Linked Issues check ✅ Passed The PR successfully addresses issue #275 by implementing wslpath-based path conversion with fallback for WSL bash compatibility.
Out of Scope Changes check ✅ Passed The changes are focused solely on fixing the SessionStart hook command for WSL bash compatibility; no unrelated or out-of-scope modifications are present.
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.


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: 1

🤖 Fix all issues with AI agents
In `@hooks/hooks.json`:
- Line 9: The command string in hooks.json currently uses single quotes around
${CLAUDE_PLUGIN_ROOT} inside the wslpath call which prevents environment
variable expansion; update the command so CLAUDE_PLUGIN_ROOT is expanded (e.g.,
use double quotes or no inner quotes) when calling wslpath, ensuring the
resulting path feeds into run-hook.cmd and session-start.sh correctly (refer to
the "command" value, the wslpath invocation, ${CLAUDE_PLUGIN_ROOT}, and
run-hook.cmd/session-start.sh).

@zeynalnia
Copy link
Author

@coderabbitai The single quotes around ${CLAUDE_PLUGIN_ROOT} are intentional and necessary.

Claude Code expands ${CLAUDE_PLUGIN_ROOT} before passing the command to the shell. So by the time the shell sees it, the command is:

wslpath -u 'C:\Users\Name With Spaces\.claude\plugins\...'

The single quotes protect against word splitting when the path contains spaces. Without them:

wslpath -u C:\Users\Name With Spaces\...
# Shell splits this into multiple arguments, causing failure

With single quotes, the entire path is treated as a single argument to wslpath, which is the correct behavior.

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

Tip

For best results, initiate chat on the files or code changes.

@zeynalnia Thank you for the clarification! You're absolutely right. I understand now that since Claude Code expands ${CLAUDE_PLUGIN_ROOT} before the shell sees the command, the single quotes are essential to protect the resulting Windows path (which may contain spaces) from word splitting.

Your explanation makes perfect sense:

  • With quotes: wslpath -u 'C:\Users\Name With Spaces\...' → single argument ✓
  • Without quotes: wslpath -u C:\Users\Name With Spaces\... → multiple arguments ✗

I appreciate you taking the time to explain Claude Code's variable expansion behavior. This is important context for understanding the correct quoting strategy in this hook system.


✏️ Learnings added
Learnt from: zeynalnia
URL: https://github.com/obra/superpowers/pull/282

Timestamp: 2026-01-17T10:04:35.426Z
Learning: In the superpowers plugin for Claude Code, when using `${CLAUDE_PLUGIN_ROOT}` in hook commands (hooks/hooks.json), single quotes around the variable are necessary because Claude Code expands variables before passing the command to the shell. The quotes protect against word splitting when the expanded path contains spaces (e.g., 'C:\Users\Name With Spaces\...').

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

When WSL's bash.exe is in PATH (either alone or before Git's bash.exe),
Claude Code passes Windows-style paths that WSL bash cannot resolve,
causing "No such file or directory" errors.

The fix attempts to convert paths using wslpath first, falling back to
the original path for non-WSL environments (native Linux/macOS or
Windows with Git Bash).

To reproduce the original issue:
- PATH contains only WSL's bash.exe path, OR
- PATH contains both WSL and Git bash.exe paths but WSL comes first

Fixes obra#275

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zeynalnia zeynalnia force-pushed the fix/windows-wsl-bash-default branch from c0833d4 to 3519d03 Compare January 18, 2026 07:42
@obra
Copy link
Owner

obra commented Jan 19, 2026

This looks like it is likely to fail on everything that's not Windows. Have you tested it on Mac OS and Linux? Is it something that we can address inside the polyglot wrapper script?

@mcm2020
Copy link

mcm2020 commented Jan 19, 2026

FWIW this workaround worked for me, with the exception that I had to use cygpath -u for the path conversion instead of wslpath.

Running in PowerShell on Windows 11 with both WSL and Git Bash installed:
/bin/bash: wslpath: command not found

@zeynalnia
Copy link
Author

@mcm2020
wslpath is a built-in command in WSL, while cygpath is built into Git Bash and MSYS2.

@obra
After more investigation and testing, this appears to be a Claude Code issue. Claude Code seems to handle hook commands inconsistently: depending on the command, it may choose to execute it via Bash, Windows CMD, or even Python.

What I’m seeing is:

  • When Git Bash is set as the default, the hook command ends up running in Windows CMD.
  • When WSL is the default, the same command is executed via Bash.
  • When WSL is the default and || is used for fallback, Claude Code ignores the first part and directly runs the second part in Windows CMD.

For example, even changing the command to something like:
dummy || \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh
still works correctly when WSL is the default.

I tried to reverse-engineer how Claude Code decides which runtime to use, but couldn’t find a consistent rule. A possible workaround could be:
\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh || \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh

This avoids relying on wslpath, but there’s no guarantee it will remain reliable across future Claude Code updates.

@obra obra added the windows label Jan 22, 2026
@obra
Copy link
Owner

obra commented Jan 22, 2026

Thank you for this contribution! However, this PR has been superseded by PR #331.

Context: We discovered that Claude Code 2.1.x changed the Windows execution model for hooks. It now auto-detects .sh files in hook commands and prepends bash automatically on Windows. This means the polyglot wrapper (run-hook.cmd) is no longer needed.

PR #331 takes a different approach:

  • Updates hooks.json to call session-start.sh directly instead of using the wrapper
  • Adds .gitattributes to enforce LF line endings
  • Deprecates run-hook.cmd with an explanatory comment

Since the polyglot wrapper is no longer invoked, the WSL path conversion in this PR is no longer necessary. Claude Code handles the bash invocation directly, and mixed Windows/Unix paths work correctly with the new approach.

I'd recommend closing this PR in favor of #331. Your investigation into the WSL path resolution issue was valuable for understanding the problem space!

@obra
Copy link
Owner

obra commented Jan 22, 2026

Closing as superseded by PR #331. Thank you for the contribution - your investigation into the WSL path resolution was helpful for understanding the problem space.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SessionStart:startup hook error

3 participants