sindresorhus / sindresorhus/execa

Execa 10 - Windows: arguments are double caret-escaped for every batch file, not just cmd-shims

Open
#1,261 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
7.6k
Forks
570
PR merge metrics
No merged PRs in 30d

Description

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 50

where 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lib/arguments/command-file.js, especially the batch-file detection around lines 26 and 50, and reproduce the issue with the shown args.cmd example on Windows. Done means ordinary .cmd/.bat files receive correctly escaped arguments while cmd-shims retain the extra escaping, with the reported regression covered by verification.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.