fix(hooks): keep inline resolver backslash-free so hooks parse on Windows

On native Windows, Claude Code runs each hook as `bash.exe -c "<command>"`
through the Windows command line. That extra unescape layer collapses the
backslashes in the WSL path conversion of the inline plugin-root resolver,
so `.replace(/\\/g,'/')` reaches node as `.replace(/\/g,'/')` and every hook
fails with `SyntaxError: missing ) after argument list`.

Build the backslash separator with String.fromCharCode(92) and split/join
instead of regex escapes, so the resolver contains no backslash. Conversion
behaviour is unchanged. Regenerate every embedded copy (hooks.json,
codex-hooks.json, commands/*.md) and refresh hooks.metadata.json
fingerprints.

Add a test that the resolver and each hook `node -e` program are
backslash-free, and run the shipped SessionStart command through bash with
backslashes collapsed the way Windows does.

Fixes #3746
This commit is contained in:
Gaurav Dubey committed 2026-10-26 18:39:04 +00:00
1 parent 25278e7d7b
commit dc9bb9f2d9
14 files changed
+115 -77

No files matched your search

+4 -2
View File
@@ -37,8 +37,10 @@ function setWSLForTesting(value) {
const WINDOWS_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
// Compact WSL conversion snippet for inline resolvers embedded in commands and hooks.
// Uses 8 backslashes in regex source so it preserves shell escaping when inlined into `node -e "..."`.
const INLINE_WSL_CONVERSION = `try{if(o.platform()==='linux'){var v=f.readFileSync('/proc/version','utf8').toLowerCase();if(v.includes('microsoft')){var m=d.match(/^([A-Za-z]):[\\\\\\\\/]/);if(m){var dl=m[1].toLowerCase();d='/mnt/'+dl+d.slice(2).replace(/\\\\\\\\/g,'/')}}}}catch(_){}`;
// Contains no backslash: on native Windows, Claude Code runs hooks as `bash.exe -c "<command>"`
// and the extra argv/MSYS unescape layer collapses backslashes, which broke regex literals (#3746).
// The backslash separator is built with String.fromCharCode(92) instead.
const INLINE_WSL_CONVERSION = `try{if(o.platform()==='linux'){var v=f.readFileSync('/proc/version','utf8').toLowerCase();if(v.includes('microsoft')){var b=String.fromCharCode(92),m=d.match(/^([A-Za-z]):/);if(m&&(d.charAt(2)==='/'||d.charAt(2)===b)){var dl=m[1].toLowerCase();d='/mnt/'+dl+d.slice(2).split(b).join('/')}}}}catch(_){}`;
/**
* Convert a Windows path to a WSL path.
+1 -1
View File
@@ -183,7 +183,7 @@ function getInlineWslConversion() {
try {
_inlineWslConversion = require('./install-targets/helpers').INLINE_WSL_CONVERSION;
} catch (_) {
_inlineWslConversion = `try{if(o.platform()==='linux'){var v=f.readFileSync('/proc/version','utf8').toLowerCase();if(v.includes('microsoft')){var m=d.match(/^([A-Za-z]):[\\\\\\\\/]/);if(m){var dl=m[1].toLowerCase();d='/mnt/'+dl+d.slice(2).replace(/\\\\\\\\/g,'/')}}}}catch(_){}`;
_inlineWslConversion = `try{if(o.platform()==='linux'){var v=f.readFileSync('/proc/version','utf8').toLowerCase();if(v.includes('microsoft')){var b=String.fromCharCode(92),m=d.match(/^([A-Za-z]):/);if(m&&(d.charAt(2)==='/'||d.charAt(2)===b)){var dl=m[1].toLowerCase();d='/mnt/'+dl+d.slice(2).split(b).join('/')}}}}catch(_){}`;
}
}
return _inlineWslConversion;