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 .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"
}
]
}
]
}
}
- 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 .sh anywhere inside command (e.g. ".../openclaude-hook.sh").
- Since
command starts with if ... (not bash ), it transforms command into bash if [ ... ]; then ....
- When
child_process.spawn(finalCommand, [], { shell: findGitBashPath() }) is invoked, Git Bash receives bash -c "bash if ...; then ...".
- 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.
Summary
On Windows (non-PowerShell mode),
execCommandHookinsrc/utils/hooks.tsattempts to auto-prependbashto hook commands that reference.shscripts.However, because the regular expression
/\.sh(\s|$|")/matches.shanywhere in the command string, complex compound shell scripts (such as cross-platform hook wrappers provided by tools like Orca) that contain.shanywhere in their conditional branches get corrupted:bashis 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
~/.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" } ] } ] } }Expected Behavior
The compound bash script should be executed directly via the configured Git Bash shell (
spawn(finalCommand, [], { shell: findGitBashPath() })) without prependingbashif it's already a compound bash script.Actual Behavior
The hook fails with:
Root Cause Analysis
In
src/utils/hooks.ts(lines 1051-1055):/\.sh(\s|$|")/matches.shanywhere insidecommand(e.g.".../openclaude-hook.sh").commandstarts withif ...(notbash), it transformscommandintobash if [ ... ]; then ....child_process.spawn(finalCommand, [], { shell: findGitBashPath() })is invoked, Git Bash receivesbash -c "bash if ...; then ...".bash if ...as a command with arguments, and encounters; thenwithout a precedingifstatement, throwingsyntax error near unexpected token 'then'.Suggested Fix
Only auto-prepend
bashif the command is actually a direct invocation of a.shfile (e.g., starts with a file path ending in.sh), rather than matching anywhere in arbitrary shell scripts. For example:Or check if the command starts with common shell keywords/operators (
if,{,case,for,while, etc.) before applying the prepend logic.