Skip to content

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

Description

@dlivxpr

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:
{
  "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):

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

  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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions