* fix(#2236): add hookShell parameter for PowerShell call operator Windows Claude Code with a PowerShell hook runner failed on every hook with 'Unexpected token' — the installer emitted bare quoted paths that PowerShell parses as string literals, not commands. The root cause was that hookCommandNeedsPowerShellCallOperator returned false unconditionally — the &/no-& decision was keyed on (platform, runtime) only, with no signal for the effective hook-execution shell. One runtime (Claude Code) hosts either Git Bash or PowerShell on Windows. Fix: thread a new hookShell parameter through the projection chain. When hookShell='powershell', the & call operator is prepended. Default (Git Bash, no prefix) is unchanged and regression-locked. * docs: backfill changeset PR number (#2261) * merge: keep up to date with next * fix: regenerate stale capability-registry after next merge
This commit is contained in:
@@ -534,6 +534,67 @@ describe('bug #3413: Shell Command Projection Module uses runtime-aware hook pol
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #2236: hookShell parameter routes PowerShell call operator', () => {
|
||||
test('hookShell=powershell on Windows returns true for any runtime', () => {
|
||||
assert.equal(
|
||||
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'claude', hookShell: 'powershell' }),
|
||||
true,
|
||||
'PowerShell hook shell must trigger the call operator regardless of runtime',
|
||||
);
|
||||
assert.equal(
|
||||
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'antigravity', hookShell: 'powershell' }),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('hookShell=bash (default) stays false — Git-Bash form preserved', () => {
|
||||
assert.equal(
|
||||
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'claude', hookShell: 'bash' }),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'claude' }),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('formatHookCommandForRuntime prepends & when hookShell=powershell', () => {
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/gsd-context-monitor.js"';
|
||||
assert.equal(
|
||||
formatHookCommandForRuntime(cmd, { platform: 'win32', runtime: 'claude', hookShell: 'powershell' }),
|
||||
`& ${cmd}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('formatHookCommandForRuntime omits & when hookShell unset (backward compat)', () => {
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/gsd-context-monitor.js"';
|
||||
assert.equal(
|
||||
formatHookCommandForRuntime(cmd, { platform: 'win32', runtime: 'claude' }),
|
||||
cmd,
|
||||
);
|
||||
});
|
||||
|
||||
test('buildHookCommand emits & prefix when hookShell=powershell', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
platform: 'win32',
|
||||
runtime: 'claude',
|
||||
hookShell: 'powershell',
|
||||
});
|
||||
assert.ok(cmd, 'buildHookCommand should return a command');
|
||||
assert.ok(cmd.startsWith('& '), `PowerShell hook command must start with '& ': ${cmd}`);
|
||||
});
|
||||
|
||||
test('buildHookCommand omits & prefix when hookShell=bash (regression lock)', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
platform: 'win32',
|
||||
runtime: 'claude',
|
||||
hookShell: 'bash',
|
||||
});
|
||||
assert.ok(cmd, 'buildHookCommand should return a command');
|
||||
assert.equal(cmd.startsWith('& '), false, `Git-Bash hook command must NOT start with '& ': ${cmd}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3413: installer hook surfaces consume runtime-aware projection', () => {
|
||||
test('buildHookCommand emits shell-neutral Claude hook command on Windows', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
|
||||
Reference in New Issue
Block a user