The three shared `command` fields in claude-codex-hooks.json used `exec node "..."`. `exec` is a bash/zsh builtin with no PowerShell equivalent. Some hosts run the `command` field through PowerShell on Windows regardless of the PowerShell-native `commandWindows` field: VS Code Copilot always does (it never reads `commandWindows`), and native Claude Code launched from Git Bash was reported doing the same (#569). PowerShell then fails on the first token with CommandNotFoundException, so every SessionStart / SubagentStart / UserPromptSubmit hook errors out on Windows. Plain `node "..."` runs natively in both bash and PowerShell. Dropping `exec` only gives up the POSIX optimization of replacing the wrapper shell with node; the wrapper-process pileup that #461 added `exec` to avoid is already handled by each hook's stdin self-exit guard (#443/#477), so there is no regression on Codex/zsh. Updated the hooks-windows regression test to assert the shell-agnostic form instead of requiring `exec`. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
523e9dc051
commit
2ba0262111
|
|
@ -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..."
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue