diff --git a/hooks/claude-codex-hooks.json b/hooks/claude-codex-hooks.json index 2cb796c..b685fd5 100644 --- a/hooks/claude-codex-hooks.json +++ b/hooks/claude-codex-hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "exec node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-activate.js\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-activate.js\"", "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\ponytail-activate.js\" }", "timeout": 5, "statusMessage": "Loading ponytail mode..." @@ -19,7 +19,7 @@ "hooks": [ { "type": "command", - "command": "exec node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-subagent.js\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-subagent.js\"", "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\ponytail-subagent.js\" }", "timeout": 5, "statusMessage": "Loading ponytail mode..." @@ -32,7 +32,7 @@ "hooks": [ { "type": "command", - "command": "exec node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-mode-tracker.js\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-mode-tracker.js\"", "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\ponytail-mode-tracker.js\" }", "timeout": 5, "statusMessage": "Tracking ponytail mode..." diff --git a/tests/hooks-windows.test.js b/tests/hooks-windows.test.js index 50a16b4..0ec878e 100644 --- a/tests/hooks-windows.test.js +++ b/tests/hooks-windows.test.js @@ -53,13 +53,23 @@ test('shared hook commands avoid POSIX-only guard syntax', () => { } }); -test('shared hook commands exec node instead of leaving a wrapper shell behind', () => { +// Issue #527 / #569: the shared `command` field must be shell-agnostic. `exec` +// is a bash/zsh builtin with no PowerShell equivalent, but some hosts run +// `command` through PowerShell on Windows regardless of the commandWindows +// field — VS Code Copilot always does (it never reads commandWindows), and +// native Claude Code launched from Git Bash was seen doing the same. `exec +// node ...` then dies on its first token with CommandNotFoundException, so +// every hook fails on Windows. Plain `node ...` runs natively in both bash and +// PowerShell. The wrapper-process pileup that #461 originally used `exec` to +// avoid is handled separately by each hook's stdin self-exit guard (#443/#477). +test('shared hook commands are shell-agnostic (no bash-only exec prefix)', () => { const commands = commandHooks() .map((h) => h.command) .filter(Boolean); assert.ok(commands.length > 0, 'expected at least one shared command entry'); for (const cmd of commands) { - assert.match(cmd, /^exec node\s+/, `command must replace the shell with node: ${cmd}`); + assert.doesNotMatch(cmd, /(^|\s)exec\s/, `command must not use the bash-only 'exec' builtin (breaks under PowerShell): ${cmd}`); + assert.match(cmd, /^node\s+/, `command must invoke node directly so it runs in both bash and PowerShell: ${cmd}`); assert.doesNotMatch(cmd, /;\s*exit 0$/, `command must not leave a shell wrapper waiting on node: ${cmd}`); } });