[GH-ISSUE #4883] [Bug] Windows: rea --update fails with spawn npm ENOENT because the bundled incur updater cannot launch npm.cmd #3034

Closed
opened 2026-10-31 00:35:12 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @imhavingasuperbadday on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4883

Before submitting

  • I searched the open and closed issues for a related report. (#4125 and #504 are the same failure class in other subsystems and are closed; no open or closed issue covers rea --update itself.)
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Setup, packaging, or upgrade

Observed behavior

On Windows, rea --update aborts before the installer runs:

Error (UPDATE_FAILED): spawn npm ENOENT

Observed at runtime with rea-agents 4.1.0 on Windows 10.0.26200 x64 (Node v24.18.0, npm 12.2.0): every attempt reproduced, with no npm output — the install command never starts, so nothing is partially installed.

Source-derived cause: the update flow uses the bundled incur@0.6.1. Its exec() (incur/dist/internal/update.js) launches the install as child_process.execFile('npm', ['install', '--global', 'rea-agents@latest']); the command string comes from incur's globalInstall(). On Windows npm is npm.cmd, which execFile cannot launch without a shell, so Node raises spawn npm ENOENT and incur surfaces it as UPDATE_FAILED. Probes actually run on this machine: execFile('npm', ['--version']) → spawn npm ENOENT; execFile('npm.cmd', …) → spawn EINVAL; execFile('npm', …, { shell: true }) → works.

Reproduction

  1. On Windows: npm install -g rea-agents@4.1.0
  2. Run rea --update
  3. Observe Error (UPDATE_FAILED): spawn npm ENOENT

Expected behavior

rea --update should run the package-manager install and either complete the update or surface the package manager's own error (e.g. npm stderr). Known-good comparison (observed): after a local one-file change to the bundled incur exec() (use cross-spawn instead of execFile), the same rea --update on the same machine completed 4.1.0 → 6.5.0.

Impact and scope

  • Windows users cannot update through rea --update when the package manager is npm or pnpm; the CLI's own recovery path is unavailable. Manual npm install -g rea-agents@latest works (observed).
  • Update checks/notifications are unaffected (they spawn node.exe), as are rea --version and the running CLI. Because the failure precedes any install activity, no evidence or artifact state is touched.
  • Frequency: 100% of attempts on the observed setup. Non-Windows scope untested here (the same call works where npm is an executable shell script).
  • Current release affected: rea-agents 6.5.0 still bundles incur@0.6.1 with the identical unguarded execFile call (source inspection of the installed copy — not a runtime run of unpatched 6.5.0).

REA version

4.1.0 (observed). Current install: 6.5.0 (source shows still affected).

Runtime and platform

Node v24.18.0; Windows 10.0.26200 x64; npm 12.2.0.

Target and artifact context

Not applicable — package upgrade path, no target artifact.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Command actually run: rea --update (rea-agents 4.1.0, Windows, Node v24.18.0).

Relevant result:

Error (UPDATE_FAILED): spawn npm ENOENT

Source references (source-derived): rea-agents bundles incur@0.6.1; incur/src/internal/update.ts L396-L403 (exec() → execFile(command, args)), incur/src/internal/pm.ts L35-L42 (globalInstall() → command: 'npm' | 'pnpm').

This is the same failure class already addressed for REA's own scripts in #4125 (and #504); the update path remains exposed because the code lives in the bundled incur copy.

Typed outcome or recovery expected

Optional: an update error that names the package-manager failure (npm stderr) rather than a spawn ENOENT, plus a working install.

Analysis or suspected cause

Optional. Upstream report filed: https://github.com/wevm/incur/issues/268 (same root cause; affects incur <= 0.7.1). Possible REA-side handling: bump to a fixed incur once available, resolve the npm CLI and execute it through process.execPath the way #4125 resolved its case, or carry a small patch for the bundled copy until then. Root cause (source-derived) is the unguarded execFile('npm', …); the 4.1.0 failure and the one-file-patch success were observed at runtime.

Additional context

Local workaround used for verification only (not proposed in this issue): the bundled incur exec() was patched to cross-spawn so rea --update could complete. A fresh 6.5.0 install replaces that patch with the original code.

Originally created by @imhavingasuperbadday on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4883 ### Before submitting - [x] I searched the open and closed issues for a related report. (#4125 and #504 are the same failure class in other subsystems and are closed; no open or closed issue covers `rea --update` itself.) - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Setup, packaging, or upgrade ### Observed behavior On Windows, `rea --update` aborts before the installer runs: ``` Error (UPDATE_FAILED): spawn npm ENOENT ``` Observed at runtime with rea-agents 4.1.0 on Windows 10.0.26200 x64 (Node v24.18.0, npm 12.2.0): every attempt reproduced, with no npm output — the install command never starts, so nothing is partially installed. Source-derived cause: the update flow uses the bundled `incur@0.6.1`. Its `exec()` (`incur/dist/internal/update.js`) launches the install as `child_process.execFile('npm', ['install', '--global', 'rea-agents@latest'])`; the command string comes from incur's `globalInstall()`. On Windows npm is `npm.cmd`, which `execFile` cannot launch without a shell, so Node raises `spawn npm ENOENT` and incur surfaces it as `UPDATE_FAILED`. Probes actually run on this machine: `execFile('npm', ['--version'])` → `spawn npm ENOENT`; `execFile('npm.cmd', …)` → `spawn EINVAL`; `execFile('npm', …, { shell: true })` → works. ### Reproduction 1. On Windows: `npm install -g rea-agents@4.1.0` 2. Run `rea --update` 3. Observe `Error (UPDATE_FAILED): spawn npm ENOENT` ### Expected behavior `rea --update` should run the package-manager install and either complete the update or surface the package manager's own error (e.g. npm stderr). Known-good comparison (observed): after a local one-file change to the bundled incur `exec()` (use `cross-spawn` instead of `execFile`), the same `rea --update` on the same machine completed 4.1.0 → 6.5.0. ### Impact and scope - Windows users cannot update through `rea --update` when the package manager is npm or pnpm; the CLI's own recovery path is unavailable. Manual `npm install -g rea-agents@latest` works (observed). - Update checks/notifications are unaffected (they spawn `node.exe`), as are `rea --version` and the running CLI. Because the failure precedes any install activity, no evidence or artifact state is touched. - Frequency: 100% of attempts on the observed setup. Non-Windows scope untested here (the same call works where npm is an executable shell script). - Current release affected: rea-agents 6.5.0 still bundles `incur@0.6.1` with the identical unguarded `execFile` call (source inspection of the installed copy — not a runtime run of unpatched 6.5.0). ### REA version 4.1.0 (observed). Current install: 6.5.0 (source shows still affected). ### Runtime and platform Node v24.18.0; Windows 10.0.26200 x64; npm 12.2.0. ### Target and artifact context Not applicable — package upgrade path, no target artifact. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Command actually run: `rea --update` (rea-agents 4.1.0, Windows, Node v24.18.0). Relevant result: ``` Error (UPDATE_FAILED): spawn npm ENOENT ``` Source references (source-derived): rea-agents bundles `incur@0.6.1`; `incur/src/internal/update.ts` L396-L403 (`exec()` → `execFile(command, args)`), `incur/src/internal/pm.ts` L35-L42 (`globalInstall()` → `command: 'npm' | 'pnpm'`). This is the same failure class already addressed for REA's own scripts in #4125 (and #504); the update path remains exposed because the code lives in the bundled incur copy. ### Typed outcome or recovery expected Optional: an update error that names the package-manager failure (npm stderr) rather than a spawn ENOENT, plus a working install. ### Analysis or suspected cause Optional. Upstream report filed: https://github.com/wevm/incur/issues/268 (same root cause; affects incur <= 0.7.1). Possible REA-side handling: bump to a fixed incur once available, resolve the npm CLI and execute it through `process.execPath` the way #4125 resolved its case, or carry a small patch for the bundled copy until then. Root cause (source-derived) is the unguarded `execFile('npm', …)`; the 4.1.0 failure and the one-file-patch success were observed at runtime. ### Additional context Local workaround used for verification only (not proposed in this issue): the bundled incur `exec()` was patched to `cross-spawn` so `rea --update` could complete. A fresh 6.5.0 install replaces that patch with the original code.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Fixed on main in 4f7b5fb60.

The root --update flag now routes through the existing verified rea update command before output handling. That shares selected environment, installation ownership, Windows-aware package-manager launch, version verification, maintenance planning and failure outcomes. Output-option values are preserved; help/check precedence stays non-installing. Ambiguous command/flag combinations are refused before Incur's installer can run. update: false alone is insufficient in the pinned Incur version, so an explicit rejecting callback closes that fallback.

Validation: 123 focused checks across seven files pass, including compiled public CLI calls with an owned rejecting npm fixture and direct serve across JSON, JSONL, YAML, TOON and Markdown. Filtering/full-output and update failure exit status are checked. Baseline argument tests give6fail/7pass; the isolated, sandboxed workflow baseline gives8fail/2pass. Static/docs/discovery1561 and pre-push checks pass. Both scoped source reviews are clear.

Six completed small workflows compare baseline canonical update against the new alias with an identical current-version answer. Complete replies are byte-identical:28estimated cl100k tokens; selected request6→7, progress0. Warm two-repeat medians: elapsed0.420→0.417s, CPU0.618→0.615s, sampled family RSS369.6→377.0MB. No true-cold/significance/speedup, real-install or whole-agent cost claim.

Test incident and correction: the first baseline bypassed a Vitest module-factory mock for external Node builtins and installed REA in the pinned Node global prefix. Its newly added global package was removed; original npm/Corepack versions were verified. The rerun uses a real builtin spy with synchronized ESM exports plus independently owned PATH, rejecting npm shim, prefix and cache. Only that corrected baseline is used as validation evidence.

Scope: native macOS CLI and owned package-manager fixtures; real Windows/npm upgrade and pnpm installation are NOT RUN. The shared updater deliberately refuses unknown installation ownership. No dependency code was patched or upgraded. Heavy tests/profiling remain stopped. Independent unbounded updater output retention is tracked in #4888 for the ongoing audit.

<!-- gh-comment-id:6241464683 --> @morluto commented on GitHub (Oct 17, 2026): Fixed on `main` in `4f7b5fb60`. The root `--update` flag now routes through the existing verified `rea update` command before output handling. That shares selected environment, installation ownership, Windows-aware package-manager launch, version verification, maintenance planning and failure outcomes. Output-option values are preserved; help/check precedence stays non-installing. Ambiguous command/flag combinations are refused before Incur's installer can run. `update: false` alone is insufficient in the pinned Incur version, so an explicit rejecting callback closes that fallback. Validation: 123 focused checks across seven files pass, including compiled public CLI calls with an owned rejecting npm fixture and direct serve across JSON, JSONL, YAML, TOON and Markdown. Filtering/full-output and update failure exit status are checked. Baseline argument tests give6fail/7pass; the isolated, sandboxed workflow baseline gives8fail/2pass. Static/docs/discovery1561 and pre-push checks pass. Both scoped source reviews are clear. Six completed small workflows compare baseline canonical `update` against the new alias with an identical current-version answer. Complete replies are byte-identical:28estimated cl100k tokens; selected request6→7, progress0. Warm two-repeat medians: elapsed0.420→0.417s, CPU0.618→0.615s, sampled family RSS369.6→377.0MB. No true-cold/significance/speedup, real-install or whole-agent cost claim. Test incident and correction: the first baseline bypassed a Vitest module-factory mock for external Node builtins and installed REA in the pinned Node global prefix. Its newly added global package was removed; original npm/Corepack versions were verified. The rerun uses a real builtin spy with synchronized ESM exports plus independently owned PATH, rejecting npm shim, prefix and cache. Only that corrected baseline is used as validation evidence. Scope: native macOS CLI and owned package-manager fixtures; real Windows/npm upgrade and pnpm installation are NOT RUN. The shared updater deliberately refuses unknown installation ownership. No dependency code was patched or upgraded. Heavy tests/profiling remain stopped. Independent unbounded updater output retention is tracked in #4888 for the ongoing audit.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3034
No description provided.