Pull Request Overview
- Opened on September 5, 2026
- Status Open
- Commit count 3 with first commit September 5, 2026
Total Delta
Open Days
Test Delta
How long has this pull request spent in each phase of its lifecycle?
| Fraction of total time | Business days | Phase |
|---|---|---|
|
|
0.0 days | Authoring 1 commit before pull request opened for review |
|
|
0.0 days | Awaiting first review |
|
|
10.0 days | Revising work with 2 commits in response to 2 reviews that left 7 comments |
Total time for pull request still awaiting merge (longer than repo's target): 10.0 business days
Make the Metro config's PowerShell path lookup lazy again, to stop spamming errors on macOS
I didn't file an issue for this yet, so I'll begin by introducing the problem before describing the solution that this PR implements.
Problem
When you try to run rnc-cli start on a Mac, in a project with react-native-windows installed, using @rnx-kit/metro-config as your Metro config, it eagerly looks for .NET and Powershell, naturally fails to find them, and makes a lot of noise:
./node_modules/.bin/rnc-cli start
/bin/sh: dotnet.exe: command not found
/bin/sh: where: command not found
/bin/sh: dotnet.exe: command not found
/bin/sh: where: command not found
/Users/jamie/Documents/git/expo-desktop/packages/expo-desktop/MyApp4/node_modules/react-native-windows/react-native.config.js: /bin/sh: dotnet.exe: command not found
/bin/sh: where: command not found
/Users/jamie/Documents/git/expo-desktop/packages/expo-desktop/MyApp4/node_modules/@react-native-windows/find-dotnet-tools/lib-commonjs/findDotnetTools.js:62
throw new Error('Unable to find pwsh.exe. It should have been made available by `yarn install`.');
^
Error: Unable to find pwsh.exe. It should have been made available by `yarn install`.
at findPowerShell (/Users/jamie/Documents/git/expo-desktop/packages/expo-desktop/MyApp4/node_modules/@react-native-windows/find-dotnet-tools/lib-commonjs/findDotnetTools.js:62:11)
at Object.<anonymous> (/Users/jamie/Documents/git/expo-desktop/packages/expo-desktop/MyApp4/node_modules/@react-native-windows/cli/lib-commonjs/utils/commandWithProgress.js:45:59)
at Module._compile (node:internal/modules/cjs/loader:1812:14)
at Object..js (node:internal/modules/cjs/loader:1943:10)
at Module.load (node:internal/modules/cjs/loader:1533:32)
at Module._load (node:internal/modules/cjs/loader:1335:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
at Module.require (node:internal/modules/cjs/loader:1556:12)
at require (node:internal/modules/helpers:152:16)
at Object.<anonymous> (/Users/jamie/Documents/git/expo-desktop/packages/expo-desktop/MyApp4/node_modules/@react-native-windows/cli/lib-commonjs/commands/autolinkWindows/autolinkWindows.js:47:31)
Node.js v24.14.0
β Validation Warning:
Unknown option "watcher.unstable_workerThreads" with value false was found.
This is probably a typing mistake. Fixing it will remove this message.
Welcome to React Native v0.81
Starting dev server on http://localhost:8081
This eager search happens when @rnx-kit/metro-config discovers platforms from the dependencies at https://github.com/microsoft/rnx-kit/blob/08312a2fec3231e27a613754baa394ce2b41086e/packages/tools-react-native/src/context.ts#L147. It ends up evaluating react-native-windows/react-native.config.js, which calls findPowerShell().
(CC @tido64 β I think it's reasonable to fix it in react-native-windows, but tagging just in case it'd be preferable to fix it on the rnx-kit side).
History
There was no such problem in [email protected]. The regression was introduced when https://github.com/microsoft/react-native-windows/pull/16075 was backported to 0.81.28 in https://github.com/microsoft/react-native-windows/pull/16235, with changes such as this one, to line 50 of packages/@react-native-windows/cli/src/utils/commandWithProgress.ts:
- export const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
+ const powershell = findPowerShell();
See how instances that used to be static strings instead become eager file system lookup, causing side-effects.
Solution
This PR makes it so that we resolve and cache the path to PowerShell only when actually about to execute a PowerShell command. i.e. it changes it from eager to lazy.
Reproduction
Repro repository, based on Expo Desktop:
# (1) Set up the project:
git clone --branch codex/reproduce https://github.com/shirakaba/rnw-powershell-discovery-repro.git
cd rnw-powershell-discovery-repro
bun install --frozen-lockfile
# (2a) Trigger `expo start` (equivalent to `rnc-cli start`):
node --run start
# (2b) Or, more minimally, just import the metro config:
node -e "require('./metro.config.js')"
# (3) Compare two branches (run `bun install` post-checkout):
# - main: problem reproduces.
# - codex/fix: problem fixed, by patching RNW.
In my case, I used Node.js 24 and Bun 1.3.11.
See the codex/fix branch, which applies this proposed change as a patch.
Backporting
While this PR is opened against main, please consider backporting to 0.81-stable so that I can release Expo Desktop in best condition.
Testing
Astra initially wrote a whole test suite for this. I think they're a bit over the top, though, so I've pushed an extra commit to drop them. If the reviewer wants, I can restore them just by reverting that commit.
- ~~10 Jest tests pass: import/serialization on simulated macOS and Windows, deferred lookup, successful caching, retry after failure, and diagnostic/repair execution. The import regressions fail against the original source on both simulated hosts.~~
- ~~CLI TypeScript build and ESLint pass; changed files formatted with Prettier.~~
- ~~Repro CI on real macOS and Windows: baseline confirms the bug; patched branch passes. Checks forbid PowerShell discovery, require empty stderr after patching, and verify both platforms remain available.~~
Changelog
Yes: Defer PowerShell discovery until a command or health check needs it, allowing CLI configuration to load without Windows build tools.
###### Microsoft Reviewers: Open in CodeFlow
Comments Threads Pending Resolution
### π‘ Changes recommended
PowerShell lookup failures bypass existing command and health-check error handling.
*Once you've addressed the issues Copilot identified, you can request another Copilot review.*
Pull request overview
Defers PowerShell discovery to avoid failures when loading RNW configuration without Windows tooling.
**Changes:**
- Lazily caches PowerShell paths during command and health-check execution.
- Adds a prerelease change record.
File summaries
| File | Description |
| ---- | ----------- |
| `commandWithProgress.ts` | Defers PowerShell discovery until command execution. |
| `healthChecks.ts` | Defers discovery until diagnostics or repair. |
| Change JSON | Records the CLI prerelease change. |
Review details
- **Files reviewed:** 3/3 changed files
- **Comments generated:** 2
- **Review effort level:** Balanced
---
π‘ Add a `code-review` agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Resolved Comment Threads
### π‘ Changes recommended
PowerShell lookup failures bypass existing command and health-check error handling.
*Once you've addressed the issues Copilot identified, you can request another Copilot review.*
Pull request overview
Defers PowerShell discovery to avoid failures when loading RNW configuration without Windows tooling.
**Changes:**
- Lazily caches PowerShell paths during command and health-check execution.
- Adds a prerelease change record.
File summaries
| File | Description |
| ---- | ----------- |
| `commandWithProgress.ts` | Defers PowerShell discovery until command execution. |
| `healthChecks.ts` | Defers discovery until diagnostics or repair. |
| Change JSON | Records the CLI prerelease change. |
Review details
- **Files reviewed:** 3/3 changed files
- **Comments generated:** 2
- **Review effort level:** Balanced
---
π‘ Add a `code-review` agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
### π‘ Changes recommended
PowerShell discovery failures lose actionable diagnostics or bypass health-check failure handling.
*Once you've addressed the issues Copilot identified, you can request another Copilot review.*
Review details
### Suppressed comments (1)
**packages/@react-native-windows/cli/src/commands/healthCheck/healthChecks.ts:82**
* `findPowerShell()` can throw when the tool is absent, but this lookup occurs before the callback's failure-handling `try`. In that case `runAutomaticFix` rejects without calling `loader.fail()` or reporting manual-installation guidance, unlike all `execa` failures below. Include discovery in the handled path and fail the loader cleanly when no executable can be found.
```
powershell ??= findPowerShell();
```
- **Files reviewed:** 3/3 changed files
- **Comments generated:** 1
- **Review effort level:** Balanced