fix(hooks): auto-prepended "bash " on Windows corrupts compound bash scripts containing .sh
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
- On Windows, configure a command hook in
~/.openclaude/settings.json(or plugin) with a compound bash statement that contains.shin 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"
}
]
}
]
}
}- 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}`
}
}- The regex
/\.sh(\s|$|")/matches.shanywhere insidecommand(e.g.".../openclaude-hook.sh"). - Since
commandstarts withif ...(notbash), it transformscommandintobash if [ ... ]; then .... - When
child_process.spawn(finalCommand, [], { shell: findGitBashPath() })is invoked, Git Bash receivesbash -c "bash if ...; then ...". - The outer bash parses
bash if ...as a command with arguments, and encounters; thenwithout a precedingifstatement, throwingsyntax 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.
Source: Gitlawb/openclaude