Fix(hooks): normalize Windows managed hook script paths (#3396)

* fix: normalize windows managed hook script paths

* chore: add changeset for codex hook paths

* docs: hyphenate windows managed hooks changeset
This commit is contained in:
Tom Boucher
2026-05-11 15:27:44 -04:00
committed by GitHub
parent e79c472d7b
commit 1f99085559
3 changed files with 33 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3396
---
**Codex Windows-managed Node hooks now use double-quoted forward-slash script paths** - migrated hook commands no longer preserve POSIX single quotes that Windows treats as literal path characters.

View File

@@ -607,6 +607,12 @@ function formatHookCommandForShell(command, opts) {
return platform === 'win32' ? `& ${command}` : command;
}
function formatManagedHookScriptToken(scriptPath, opts) {
const platform = (opts && opts.platform) || process.platform;
if (platform !== 'win32') return null;
return JSON.stringify(scriptPath.replace(/\\/g, '/'));
}
function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
if (!settings || !settings.hooks || !absoluteRunner) return false;
if (!opts) opts = {};
@@ -680,7 +686,8 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
if (platform !== 'win32' || hadPowerShellCallOperator) continue;
}
h.command = formatHookCommandForShell(`${absoluteRunner} ${scriptToken}`, opts);
const safeScriptToken = formatManagedHookScriptToken(scriptPath, opts) || scriptToken;
h.command = formatHookCommandForShell(`${absoluteRunner} ${safeScriptToken}`, opts);
changed = true;
}
}

View File

@@ -335,6 +335,26 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner);
assert.equal(changed, true);
});
test('normalizes single-quoted Windows managed hook paths to double-quoted forward-slash paths (#3392)', () => {
const settings = {
hooks: {
PreToolUse: [{
hooks: [{
type: 'command',
command: "node 'C:\\Users\\me\\.codex\\hooks\\gsd-prompt-guard.js'",
}],
}],
},
};
const runner = '"C:/nvm4w/nodejs/node.exe"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32' });
assert.equal(changed, true);
assert.equal(
settings.hooks.PreToolUse[0].hooks[0].command,
'& "C:/nvm4w/nodejs/node.exe" "C:/Users/me/.codex/hooks/gsd-prompt-guard.js"',
);
});
});
describe('Bug #2979 (#3002 CR): resolveNodeRunner returns null when execPath unavailable', () => {