Execa 10 - Windows: arguments are double caret-escaped for every batch file, not just cmd-shims
Draft issue for sindresorhus/execa
Title: Windows: arguments are double caret-escaped for every batch file, not just cmd-shims
Since v10, execa escapes arguments twice for every .cmd/.bat file on Windows. That is right for npm cmd-shims, which re-expand %* through a second cmd.exe parse, but wrong for batch files that read their own arguments: they get the leftover carets and fail. cross-spawn, used through v9, only double-escaped for shims under node_modules/.bin, so this is a regression from #1251.
Steps to reproduce
args.cmd (CRLF):
@echo off
if "%~1" == "-f" (
echo file is %~2
)import {execa} from 'execa';
await execa('./args.cmd', ['-f', String.raw`C:\repo\pom.xml`], {stdio: 'inherit'});Expected (execa 9.6.1): file is C:\repo\pom.xml
Actual (execa 10.0.1): The syntax of the command is incorrect., exitCode: 255
Cause
lib/arguments/command-file.js decides from the extension alone:
const batchFileRegExp = /\.(?:bat|cmd)$/i; // line 26
const isDoubleEscape = resolvedFile !== undefined && batchFileRegExp.test(resolvedFile); // line 50where cross-spawn keyed it off the file being a shim (/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i). The command lines differ by one level of escaping:
9.6.1 cmd.exe /d /s /c "…\args.cmd ^"-f^" ^"C:\repo\pom.xml^""
10.0.1 cmd.exe /q /d /s /c "…\args.cmd ^^^"-f^^^" ^^^"C:\repo\pom.xml^^^""cmd.exe consumes one level when invoking the batch file; a shim consumes the second while expanding %*, which is why shims still work. A batch file that tests its arguments instead sees %1 as ^"-f^", so if "%~1" == "-f" ( expands to if "^"-f^"" == "-f" ( — a syntax error.
Environment
execa 10.0.1 (works on 9.6.1), Node.js 22/24/26, windows-latest GitHub Actions runner.
Found through Maven's mvn.cmd, which parses its own -f argument exactly like the snippet above: it broke every Windows CI job in Azure/typespec-azure and forced a pin back to execa@^9.6.1.
Source: sindresorhus/execa