'C:\Program' is not recognized as an internal or external command
Verified on Windows 11, Node 24.20.0 · OpusMill
operable program or batch file.
Nothing is wrong with your installation and no file is missing.
Something ran a program whose path contains a space — almost
always C:\Program Files\... — through
cmd.exe without quoting it. cmd splits the line at
the first space, so the command name became C:\Program
and the rest became arguments.
In a batch file, a shortcut or a PATH entry: wrap the path in double quotes.
In Node: remove shell: true and spawn
the executable directly. Do not just add quotes — that fixes the
error you can see and leaves
the one you cannot.
What is actually happening
cmd.exe has no concept of an escaped space. It takes everything up to
the first space as the name of the thing to run. A path like
C:\Program Files\nodejs\node.exe is therefore read as the
command C:\Program, with Files\nodejs\node.exe
as its first argument.
Here is that happening in a real .bat file on Windows 11.
The executable exists at the full path in both runs; the only difference
is the quoting:
--- unquoted --- C:\...\Program Files Probe\nodejs\node.exe --version 'C:\...\Program' is not recognized as an internal or external command, operable program or batch file. errorlevel=9009 --- quoted --- "C:\...\Program Files Probe\nodejs\node.exe" --version v24.20.0 errorlevel=0
9009 is cmd.exe's code for “I could not find that
command”. If you arrived here searching for that number, this is
what it means: the command name cmd tried to resolve was not the one you
thought you wrote.
In Node, the cause is nearly always shell: true
Without shell: true, Node hands the executable path to
Windows directly and spaces are not a problem — there is no shell
to split anything. Adding shell: true turns your carefully
built path back into a string for cmd.exe to re-parse:
const exe = path.join(process.env.ProgramFiles, "nodejs", "node.exe");
spawnSync(exe, ["--version"]); // works
spawnSync(exe, ["--version"], { shell: true }); // 'C:\Program' is not recognized
The usual way this gets into a codebase is that shell: true
was added to solve a different Windows problem — running
a .cmd file, which
throws EINVAL without it. The
fix for that bug is the cause of this one.
Note what the failure looks like from your code's point of view:
const r = spawnSync(exe, ["--version"], { shell: true });
r.status 1
r.error undefined <-- there is no error object
This is a non-zero exit, not a thrown error. An
if (err) return check does not see it, and neither does a
try/catch. If you are not inspecting the exit code, the
call fails and your program carries on as though it had worked.
The quiet version of this bug, which is worse
The obvious fix is to quote the path yourself. That does work for the
path. It does not work for the arguments, because with
shell: true Node concatenates the arguments onto the
command string without escaping them — so cmd.exe splits those on
spaces too.
The result is a call that succeeds while doing the wrong thing. All four runs below are on Windows 11, Node 24.20.0:
| Call | Exit | What the program received |
|---|---|---|
quoted path, argument alpha |
0 | alpha — correct |
quoted path, argument two words |
0 | two — silently truncated at the space |
| quoted path, argument quoted as well | 0 | two words — correct |
no shell: true, nothing quoted |
0 | two words — correct |
Row two is the one to worry about. Exit code 0. No error. No warning. A filename with a space in it, a commit message, a search term, a person's name — each arrives at the child process cut off at the first space, while every check you have says the call succeeded. The loud version of this bug at least tells you something is wrong.
Row four is why the real fix is to remove shell: true
rather than to add quotes. With no shell involved there is nothing to
re-parse, so neither the path nor the arguments need quoting at all.
Node itself now warns about this
Running any of the shell: true variants above on Node 24
prints:
(node:26032) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
“Not escaped, only concatenated” is the same mechanism
described from the security side. If an argument can contain
& or |, cmd.exe reads it as a command
separator, and the truncation problem becomes a command injection
problem. It is one line of code either way.
Which fix to use
| Situation | Do this |
|---|---|
A .bat file, a shortcut, a PATH entry, a scheduled task |
Quote the path: "C:\Program Files\...". Nothing is concatenating arguments for you here, so quoting is the whole fix. |
Node, spawning a real .exe |
Drop shell: true and pass the path and the arguments exactly as they are. |
Node, and shell: true was there to run a .cmd |
Resolve the real executable and spawn that; or keep shell: true and quote both the path and every argument yourself. See the EINVAL page. |
| An environment variable holding a path | Quote it at the point of use. %ProgramFiles% expands to a path containing a space on a default Windows install. |
One thing not to do: move the program somewhere without a space in the path. That makes the symptom go away on your machine and leaves the bug in place for everyone else, which is how this survives in shipped software for years.
Check your own project for the rest of this class of bug.
Paste your code or your package.json into
the browser checker — nothing is uploaded,
it runs on your machine — or run
npx github:Hackierz/winbreak over the whole repository.
--fix repairs the npm scripts that have one obvious
answer and refuses the ones that need a human.
Other errors in the same family:
- The fix for 'NODE_ENV' is not recognized
- The fix for 'rm' is not recognized
- The fix for spawn EINVAL on a .cmd
- The fix for ENOENT on node_modules/.bin
- Path too long on Windows
- EPERM on Windows
- Cannot find module, but only in CI
Background: I scanned the 600 most-downloaded
npm CLI packages — 17.4% have a package.json
script that cannot run on Windows.