#2195·openclaude

fix(hooks): auto-prepended "bash " on Windows corrupts compound bash scripts containing .sh

Author: dlivxprCreated Sep 2, 2026Updated Sep 2, 2026

Summary

On Windows (non-PowerShell mode), execCommandHook in src/utils/hooks.ts attempts to auto-prepend bash to hook commands that reference .sh scripts. However, because the regular expression /\.sh(\s|$|")/ matches .sh anywhere in the command string, complex compound shell scripts (such as cross-platform hook wrappers provided by tools like Orca) that contain .sh anywhere in their conditional branches get corrupted: bash is prepended to the whole script (e.g., bash if [ -z "${HOME-}" ]; then ...), causing a syntax error: /usr/bin/bash: -c: line 1: syntax error near unexpected token 'then' and completely blocking the hook execution (UserPromptSubmit operation blocked by hook).

Steps to Reproduce

  1. On Windows, configure a command hook in ~/.openclaude/settings.json (or plugin) with a compound bash statement that contains .sh in any conditional branch, for example:
json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "if [ -z \"${HOME-}\" ]; then printf '{}\\n'; else if [ -f \"${HOME-}/script.sh\" ]; then /bin/sh \"${HOME-}/script.sh\"; else printf '{}\\n'; fi; fi"
          }
        ]
      }
    ]
  }
}
  1. Start OpenClaude and submit any prompt.

Expected Behavior

The compound bash script should be executed directly via the configured Git Bash shell (spawn(finalCommand, [], { shell: findGitBashPath() })) without prepending bash if it's already a compound bash script.

Actual Behavior

The hook fails with:

● UserPromptSubmit operation blocked by hook:
  [if [ -z ... ]: /usr/bin/bash: -c: line 1: syntax error near unexpected token `then'
  /usr/bin/bash: -c: line 1: `bash if [ -z "${HOME-}" ]; then ...`

Root Cause Analysis

In src/utils/hooks.ts (lines 1051-1055):

typescript
  // On Windows (bash only), auto-prepend `bash` for .sh scripts so they
  // execute instead of opening in the default file handler. PowerShell
  // runs .ps1 files natively — no prepend needed.
  if (isWindows && !isPowerShell && command.trim().match(/\.sh(\s|$|")/)) {
    if (!command.trim().startsWith('bash ')) {
      command = `bash ${command}`
    }
  }
  1. The regex /\.sh(\s|$|")/ matches .sh anywhere inside command (e.g. ".../openclaude-hook.sh").
  2. Since command starts with if ... (not bash ), it transforms command into bash if [ ... ]; then ....
  3. When child_process.spawn(finalCommand, [], { shell: findGitBashPath() }) is invoked, Git Bash receives bash -c "bash if ...; then ...".
  4. The outer bash parses bash if ... as a command with arguments, and encounters ; then without a preceding if statement, throwing syntax error near unexpected token 'then'.

Suggested Fix

Only auto-prepend bash if the command is actually a direct invocation of a .sh file (e.g., starts with a file path ending in .sh), rather than matching anywhere in arbitrary shell scripts. For example:

typescript
  if (isWindows && !isPowerShell) {
    const trimmed = command.trim()
    if (/^(?:\.\/|\.\\|[\w\-./\\]+\.sh)(?:\s|$)/.test(trimmed) && !trimmed.startsWith('bash ')) {
      command = `bash ${command}`
    }
  }

Or check if the command starts with common shell keywords/operators (if , { , case , for , while , etc.) before applying the prepend logic.