claude-code - 💡(How to fix) Fix Windows: SessionStart hooks that invoke bash fail (exit 127, bash: command not found)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On Windows, any hook command whose first token resolves to bash fails with exit 127 and /usr/bin/bash: line 1: bash: command not found. The hook's target script never executes. Hook commands that are a bare, PATH-resolvable Windows executable work fine. This makes the official semgrep plugin throw a SessionStart error on every startup/resume/clear/compact.

Error Message

On Windows, any hook command whose first token resolves to bash fails with exit 127 and /usr/bin/bash: line 1: bash: command not found. The hook's target script never executes. Hook commands that are a bare, PATH-resolvable Windows executable work fine. This makes the official semgrep plugin throw a SessionStart error on every startup/resume/clear/compact. 3. Observe a SessionStart:startup hook error every time. Claude Code appears to wrap every hook command in Git Bash (/usr/bin/bash -c "<command>"). When <command> itself invokes bash (a nested/second bash), that nested invocation cannot be resolved — note the error reports the failed program as bare bash regardless of whether the command specified /usr/bin/bash or a full Windows path, suggesting the inner program token is normalized/mangled to bash and then not found on the wrapper's PATH. A bare exe like semgrep resolves via the Windows PATH and works, so it is specifically bash-invoking hook commands that break. (This is a regression in behavior: an earlier workaround of using absolute /usr/bin/bash previously worked on this box and no longer does.)

  • The official semgrep plugin emits a SessionStart error on every session event on Windows.

Root Cause

Claude Code appears to wrap every hook command in Git Bash (/usr/bin/bash -c "<command>"). When <command> itself invokes bash (a nested/second bash), that nested invocation cannot be resolved — note the error reports the failed program as bare bash regardless of whether the command specified /usr/bin/bash or a full Windows path, suggesting the inner program token is normalized/mangled to bash and then not found on the wrapper's PATH. A bare exe like semgrep resolves via the Windows PATH and works, so it is specifically bash-invoking hook commands that break. (This is a regression in behavior: an earlier workaround of using absolute /usr/bin/bash previously worked on this box and no longer does.)

Fix Action

Workaround

Remove the bash-invoking hook from the plugin's hooks.json (lives in the version-numbered plugin cache, so it's overwritten on plugin update). There is no per-hook disable in settings, so this recurs after updates.

Code Example

{ "type": "command", "command": "/usr/bin/bash \"${CLAUDE_PLUGIN_ROOT}/scripts/check_version.sh\"" }

---

exitCode: 127
command: /usr/bin/bash "${CLAUDE_PLUGIN_ROOT}/scripts/check_version.sh"
stderr:  Failed with non-blocking status code: /usr/bin/bash: line 1: bash: command not found
RAW_BUFFERClick to expand / collapse

Environment

  • Claude Code v2.1.157 (CLI)
  • Windows 11 Pro (10.0.26200)
  • Git for Windows (user install): C:\Users\<user>\AppData\Local\Programs\Git\usr\bin\bash.exe (GNU bash 5.2.37 msys)
  • Repro plugin: official semgrep plugin v0.5.3 (SessionStart hook check_version.sh)

Summary

On Windows, any hook command whose first token resolves to bash fails with exit 127 and /usr/bin/bash: line 1: bash: command not found. The hook's target script never executes. Hook commands that are a bare, PATH-resolvable Windows executable work fine. This makes the official semgrep plugin throw a SessionStart error on every startup/resume/clear/compact.

Steps to reproduce

  1. On Windows with Git Bash, enable the official semgrep plugin (v0.5.3).
  2. Start/resume/clear a session. Its hooks.json includes:
    { "type": "command", "command": "/usr/bin/bash \"${CLAUDE_PLUGIN_ROOT}/scripts/check_version.sh\"" }
  3. Observe a SessionStart:startup hook error every time.

Expected: the hook runs check_version.sh and exits 0. Actual: exit 127, stderr /usr/bin/bash: line 1: bash: command not found. The script body never runs.

Decisive evidence

I tested three ways of spelling the bash invocation via temporary SessionStart hooks; all fail identically, while a bare executable in the same hook set succeeds:

Hook commandResult
/usr/bin/bash "<script>" (semgrep's form)exit 127, bash: command not found
bash "<script>" (bare)exit 127, identical
"C:\Users\<user>\AppData\Local\Programs\Git\usr\bin\bash.exe" "<script>" (full Windows path)exit 127, identical
semgrep mcp -k inject-secure-defaults (bare exe)exit 0

Transcript hook records (attachment.type: hook_non_blocking_error):

exitCode: 127
command: /usr/bin/bash "${CLAUDE_PLUGIN_ROOT}/scripts/check_version.sh"
stderr:  Failed with non-blocking status code: /usr/bin/bash: line 1: bash: command not found

A probe script that simply writes a file produced no file — confirming failure is upstream of the target script, at the bash invocation itself.

Analysis

Claude Code appears to wrap every hook command in Git Bash (/usr/bin/bash -c "<command>"). When <command> itself invokes bash (a nested/second bash), that nested invocation cannot be resolved — note the error reports the failed program as bare bash regardless of whether the command specified /usr/bin/bash or a full Windows path, suggesting the inner program token is normalized/mangled to bash and then not found on the wrapper's PATH. A bare exe like semgrep resolves via the Windows PATH and works, so it is specifically bash-invoking hook commands that break. (This is a regression in behavior: an earlier workaround of using absolute /usr/bin/bash previously worked on this box and no longer does.)

Impact

  • The official semgrep plugin emits a SessionStart error on every session event on Windows.
  • Any plugin/user hook that shells out to a script via bash is broken on Windows. Non-blocking, but persistent and confusing.

Workaround

Remove the bash-invoking hook from the plugin's hooks.json (lives in the version-numbered plugin cache, so it's overwritten on plugin update). There is no per-hook disable in settings, so this recurs after updates.

Suggested fix

When executing hook commands on Windows, ensure the wrapper's environment can resolve a nested bash invocation (preserve bash/Git usr/bin on the hook PATH, or don't normalize the inner interpreter token), so command: "/usr/bin/bash <script>" works as plugin authors expect.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING