Skip to content

fix(dev): start pnpm through a shell on Windows - #184

Open
mianawais78 wants to merge 1 commit into
theam:mainfrom
mianawais78:fix/windows-pnpm-spawn
Open

fix(dev): start pnpm through a shell on Windows#184
mianawais78 wants to merge 1 commit into
theam:mainfrom
mianawais78:fix/windows-pnpm-spawn

Conversation

@mianawais78

Copy link
Copy Markdown

Summary

Fixes pnpm dev on Windows, where it aborted with Error: spawn EINVAL before any service started.

scripts/dev.mjs resolves the package manager to pnpm.cmd on win32, but run() spawned it without a shell. Since the fix for CVE-2024-27980 (Node 18.20 / 20.12), child_process.spawn refuses to launch a .cmd or .bat file unless shell is set. Every pnpm step in the script was affected — install, the shared-package build, migrate, seed and dev:services — and the first one ended the run.

The throw is also synchronous, so the existing child.once("error", ...) handler never ran and the friendly Could not start <label> hint was replaced by a raw stack trace.

shell is applied only to .cmd and .bat commands on win32, so real executables such as docker keep their current argument handling and POSIX platforms are unchanged. The predicate is exported so the behavior is testable without spawning processes.

Closes #182

Test plan

  • node --test scripts/dev.test.mjs — new usesWindowsCmdShell assertions cover .cmd and .bat on win32, plain commands on win32, and .cmd on linux
  • Verified on Windows 10 / Node v22.23.2 that spawning pnpm.cmd without a shell throws EINVAL synchronously, and succeeds with shell: true
  • Verified docker still spawns without a shell, so the compose steps are unaffected
  • pnpm dev now brings up Postgres, MinIO, API (:4400), worker, gateway (:4410), web (:3400) and docs (:3500) on Windows
  • Confirm on macOS/Linux that usesWindowsCmdShell returns false and behavior is byte-for-byte unchanged

Note for reviewers

scripts/dev.test.mjs has one failure on Windows that is pre-existing and unrelated to this change: the test asserting the generated .env mode is 0o600 sees 0o666 (expected: 384, actual: 438), because Windows does not honor POSIX file modes. That assertion is present on main. I left it alone to keep this PR focused, but I'm happy to address it here or in a separate issue if you prefer.

@mianawais78

Copy link
Copy Markdown
Author

Reproduction detail

Verified on Windows 10 (10.0.26200), Node v22.23.2, pnpm 11.20.0, from ae68401.

Spawning the three relevant command shapes directly:

pnpm.cmd no-shell (current)  : SYNC THROW code=EINVAL
pnpm.cmd shell:true (fix)    : exited code=0
docker   no-shell            : exited code=0

Two details worth noting for review:

  1. The throw is synchronous, not an error event. In run() the spawn call sits inside the promise executor, so child.once("error", ...) is never reached and the Could not find <command> / Could not start <label> messaging is bypassed entirely. What a Windows user actually sees is a bare Error: spawn EINVAL with an internal Node stack:

    Error: spawn EINVAL
        at ChildProcess.spawn (node:internal/child_process:420:11)
        at spawn (node:child_process:787:9)
      errno: -4071, code: 'EINVAL', syscall: 'spawn'
    
  2. docker must stay shell-free. It is a real .exe and spawns fine today, so widening shell to every command would change quoting behavior for the compose invocations for no benefit. That is why the predicate is narrowed to .cmd / .bat on win32 rather than keyed on platform alone.

After the change, pnpm dev completes its full sequence on Windows and the stack answers on :3400, :4400, :4410 and :3500, with /health reporting {"ok":true,"version":"0.3.0","db":"ok"}.

On Windows the development script resolves the package manager to
`pnpm.cmd`, but `spawn` refuses to launch a `.cmd` batch file without a
shell. Node raises `EINVAL` synchronously, so the promise in `run` never
reaches its `error` handler and the friendly "Could not start" hint is
replaced by a raw stack trace. The whole stack fails before Postgres,
the API, or the web app start, which makes `pnpm dev` unusable on a
Windows machine.

Pass `shell` only for `.cmd` and `.bat` commands on win32 so real
executables such as `docker` keep their current argument handling and
POSIX platforms are untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mianawais78
mianawais78 force-pushed the fix/windows-pnpm-spawn branch from 56c0ba9 to c87afc1 Compare August 19, 2026 00:21
@jungle-lethanh

Copy link
Copy Markdown

I reproduced a related Windows child-process problem in scripts/verify.mjs: pnpm verify fails immediately with spawnSync pnpm ENOENT, before lint starts. PR #184 appears scoped to scripts/dev.mjs, and no active PR currently touches verify.mjs. Would a separate focused issue and PR for the verification command be welcome?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pnpm dev fails on Windows with spawn EINVAL

2 participants