Skip to content

fix(scripts): make the Windows CI job green - #12

Merged
DKoenig9 merged 2 commits into
mainfrom
fix/windows-ci
Sep 23, 2026
Merged

DKoenig9 merged 2 commits into
mainfrom
fix/windows-ci

Conversation

@DKoenig9

Copy link
Copy Markdown
Contributor

The Windows job has been red on main since 15.09. There were three red spots with two different causes, all measured on the runner (Node 24.20.0) before any fix. With this branch, every step of the job is green except pnpm run check. That step stops only at the audit, which gives the same result on macOS (see below). Draft: no merge and no release without Daniel.

Cause 1: package-manager-pin assumed a path that doesn't exist on Windows

The test ran "<prefix>\bin\pnpm" --version. npm puts global launchers directly into the prefix on Windows. Probe output from the runner:

prefix top-level: node_modules, pn, pn.cmd, pn.ps1, pnpm, pnpm.cmd, pnpm.ps1, pnpx, …
exists prefix\bin: false
"<prefix>\bin\pnpm" --version -> status=1, stderr="The system cannot find the path specified."
"<prefix>\pnpm" --version    -> 11.14.0

The test now accepts either layout, but requires exactly one launcher inside the prefix, so it never runs a pnpm it finds on PATH. No shipped code assumes a bin path: outside test/, only the #!/usr/bin/env node shebangs contain one.

Cause 2: npm/pnpm started without a shell (configuredRegistry() and check:manifest)

On Windows, npm and pnpm are .cmd shims. Probe output:

execFileSync('pnpm', ['--version'])  -> code=ENOENT stdout="" stderr=""
execFileSync('npm',  ['--version'])  -> code=ENOENT stdout="" stderr=""
execFileSync('npm.cmd', …)           -> code=EINVAL   (CVE-2024-27980)
execFileSync('npm', …, {shell:true}) -> 11.19.0, but Node 24 warns DEP0190 (args concatenated unescaped)
  • configuredRegistry() swallowed the error and returned "".
  • check:manifest printed only stdout + stderr. Both are empty when the process never starts, so the failure message was empty.

scripts/lib/run-command.mjs now starts these commands through cross-spawn 7.0.6, pinned exactly like the lenne.tech CLI (spawnCmdSync). Its errors always name the command and the cause. Same PATH without npm, before and after:

before: [package-manifest] `npm pack --dry-run` failed:
        (empty line)
after:  [package-manifest] `npm pack --dry-run` failed:
        `npm pack --dry-run --ignore-scripts --json` could not start (ENOENT)

check-consumer-build.mjs (run by release) had the same pattern and uses the helper too.

Also: killTree on Windows

The check wrapper's watchdog walked the process tree with pgrep -P, which doesn't exist on Windows, so it ended only the shell and left the test workers running. killTreePlan is copied verbatim from lt-monorepo origin/main (taskkill /PID <pid> /T /F). It lives in scripts/lib/process-tree.mjs because this repo's check.mjs runs main() on import, which makes any function inside it untestable. Nothing in the tests sends a signal or starts taskkill: they check the plan itself and, at source level, the call site.

Tests and what they prove

  • test/run-command.test.ts, test/process-tree.test.ts, and the updated pin test.
  • Mutations checked locally. Each of these turns at least one test red: the report dropping the reason, a spawn error going undetected, the exit status being ignored, the wrong platform key, dropping /F, bypassing the call site, falling through to pgrep.
  • Which safeguards depend on the platform: the shim cases (starting npm/pnpm, the pin layout) pass trivially on macOS/Linux. A regression to execFileSync or to the bin path goes red only in the Windows job, so that job run is the proof for those cases.
  • Windows run 35836217726: 453/453 tests (the pin provisioning test ran, 12 s), manifest ok — 100 files, and format, lint, types and build all green.

Not part of this PR

pnpm audit reports one moderate finding (devalue < 5.9.1, GHSA-9rgm-9g3h-6x36), identical on Windows and macOS. It's a package question, not a Windows one, so it stays out of this PR.

🤖 Generated with Claude Code

Three red spots on the Windows job, two causes, all measured on the
runner (Node 24.20.0) before the fix:

1. package-manager-pin ran "<prefix>\bin\pnpm". npm installs global
   launchers directly into the prefix on Windows: the prefix held
   pnpm, pnpm.cmd, pnpm.ps1 and no bin directory ("exists prefix\bin:
   false"); "<prefix>\pnpm" --version answered 11.14.0. The test now
   accepts either layout, but exactly one launcher inside the prefix.
   No shipped code assumes a bin path.

2. configuredRegistry() and 3. check:manifest started npm/pnpm with
   execFileSync and no shell. Both are .cmd shims on Windows:
   execFileSync('npm') -> ENOENT, execFileSync('npm.cmd') -> EINVAL
   (CVE-2024-27980), shell:true -> DEP0190. The registry lookup
   swallowed the error into "", and check:manifest printed only
   stdout + stderr, which are empty when the process never started.

scripts/lib/run-command.mjs starts them through cross-spawn, pinned to
7.0.6 like the lenne.tech CLI (spawnCmdSync). Its errors always name
the command and the cause, e.g. "`npm pack --dry-run --ignore-scripts
--json` could not start (ENOENT)" where the old script printed nothing.
check-consumer-build.mjs had the same pattern and uses it too.

Also adopts killTreePlan verbatim from lt-monorepo origin/main: the
watchdog's pgrep walk does not exist on Windows and ended only the
shell. It lives in scripts/lib/process-tree.mjs because check.mjs runs
main() on import. Tests assert the plan and the call site; nothing
sends a signal or starts taskkill.

pnpm audit reports the same single moderate finding (devalue,
GHSA-9rgm-9g3h-6x36) on Windows and macOS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DKoenig9
DKoenig9 marked this pull request as ready for review September 23, 2026 09:40
@DKoenig9
DKoenig9 merged commit 995a4d1 into main Sep 23, 2026
2 checks passed
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.

1 participant