Skip to content

fix(core): resolve main worktree root without a Windows verbatim prefix - #36649

Merged
AgentEnder merged 1 commit into
masterfrom
fix/35637-worktree-verbatim-path
Aug 13, 2026
Merged

AgentEnder merged 1 commit into
masterfrom
fix/35637-worktree-verbatim-path

Conversation

@AgentEnder

Copy link
Copy Markdown
Member

Current Behavior

On Windows, running any cache-touching target from inside a git worktree with Nx Cloud enabled crashes after the tasks themselves have already succeeded:

 NX   Successfully ran target build for 3 projects

 NX   EISDIR: illegal operation on a directory, lstat 'C:'

Error: EISDIR: illegal operation on a directory, lstat 'C:'
    at Object.realpathSync (node:fs:2729:25)
    at toRealPath (node:internal/modules/helpers:65:13)
    at tryFile (node:internal/modules/cjs/loader:589:10)
    at tryPackage (node:internal/modules/cjs/loader:548:16)
    at Module._findPath (node:internal/modules/cjs/loader:823:18)
    at Module._resolveFilename (node:internal/modules/cjs/loader:1487:27)

This is a regression introduced in 22.7 and still present on 23.1.1. It makes Nx Cloud effectively unusable on Windows for anyone working in a worktree. The only workarounds are NX_NO_CLOUD=true or setting NX_CACHE_DIRECTORY by hand.

Root cause

22.7 added worktree-shared caching, which routed cacheDir through getMainWorktreeRoot. That function resolves the main repo root with Rust's std::fs::canonicalize — which on Windows always returns a verbatim (extended-length) path:

\\?\C:\Users\Viktor\work\nx_test\nx_issue_demo\org

path.win32.join treats the device namespace as opaque and preserves that prefix, so it survives all the way down:

get_main_worktree_root  ->  cacheDir  ->  join(cacheDir, 'cloud')  ->  require(fullPath)
     (worktree.rs)         (cache-directory.ts)   (nx-cloud/update-manager.ts:185)   (:98, :114, :139, :155)

Node's CJS resolver does not recognize the drive boundary in \\?\C:\, so Module._nodeModulePaths walks past the filesystem root and ends up calling realpathSync('C:') — hence EISDIR.

The underlying defect is a long-standing Node bug (nodejs/node#60438, open for months; see also #60435, #61165). Rather than wait on it, Nx should stop emitting the path form Node mishandles.

Expected Behavior

Targets run normally in a worktree with Nx Cloud enabled.

get_main_worktree_root now canonicalizes via dunce instead of std:

let abs_path = dunce::canonicalize(&abs_path).unwrap_or(abs_path);

dunce was already a dependency of the crate and already used elsewhere in the native code (watch_filterer.rs), so this adds nothing new to the dependency tree.

Importantly, this is not a blind prefix strip. dunce removes the \\?\ prefix only when the result is still a valid Win32 path, and deliberately keeps it otherwise — so \\?\UNC\server\share is not mangled into a broken relative path, and paths that genuinely require the prefix are left intact.

This also adds the first tests for resolve_main_worktree_root, which previously had none: one covering the main-repo case, one covering resolution from a linked worktree plus the absence of a verbatim prefix.

Notes for reviewers

Two limitations worth knowing, both deliberate:

  1. dunce keeps the prefix when the path exceeds 260 characters or contains a reserved DOS name (CON, NUL, COM1…). Those workspaces will still hit this crash. There is no clean fix on the Nx side — such a path genuinely needs the prefix to work with Win32 APIs — so the residual case is left to the upstream Node fix.

  2. The new tests cannot fail in CI. dunce::canonicalize is identical to std::fs::canonicalize on non-Windows, and test-native (cargo test, reached via test → dependsOn: ["test-native", ...]) runs on Linux only — there is no Windows Rust job. I verified this by reverting the fix locally and confirming the tests still passed on macOS. They add real first-time coverage of worktree resolution and document the constraint, but they are not a regression guard for this platform. A Windows canary would be valuable before merge — several people on the issue offered to test a fix.

Related Issue(s)

Fixes #35637


View Polygraph session ↗

- Use dunce::canonicalize instead of std::fs::canonicalize in
  get_main_worktree_root. std always returns a `\\?\` verbatim path on
  Windows, and path.win32.join preserves that prefix downstream.
- The value flows into cacheDir, so the Nx Cloud bundle is require()d from a
  verbatim path. Node's module resolution does not recognize the drive
  boundary in `\\?\C:\`, walks past the root, and fails with
  `EISDIR: illegal operation on a directory, lstat 'C:'`.
- dunce strips the prefix only when the result is still a valid Win32 path,
  so paths that genuinely need it (over 260 chars, reserved DOS names) keep
  it rather than being silently broken.
- Add regression tests for resolve_main_worktree_root, which had none.

Fixes #35637
@netlify

netlify Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 1168a29
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a7cb777e7c5b100076319e6
😎 Deploy Preview https://deploy-preview-36649--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 1168a29
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a7cb777e828760008a86454
😎 Deploy Preview https://deploy-preview-36649--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 1168a29

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 55m 45s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 55s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 16s View ↗
nx-cloud record -- nx format:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-12 19:12:03 UTC

@AgentEnder
AgentEnder marked this pull request as ready for review August 12, 2026 18:20
@AgentEnder
AgentEnder requested a review from a team as a code owner August 12, 2026 18:20
@markwhitfeld

Copy link
Copy Markdown

@AgentEnder Thank you so much for looking into this issue. This has been a hard blocker for us.
Firstly, please let me know if you would like some help testing this on windows, and what process I can follow to get this package to my local.
Secondly, do you think a hotfix for this could be cherry-picked to the previous minor versions since this regression was introduced?

@AgentEnder
AgentEnder merged commit 385957e into master Aug 13, 2026
26 checks passed
@AgentEnder
AgentEnder deleted the fix/35637-worktree-verbatim-path branch August 13, 2026 17:59
FrozenPandaz pushed a commit that referenced this pull request Aug 21, 2026
…ix (#36649)

## Current Behavior

On Windows, running any cache-touching target from inside a **git
worktree** with **Nx Cloud enabled** crashes after the tasks themselves
have already succeeded:

```
 NX   Successfully ran target build for 3 projects

 NX   EISDIR: illegal operation on a directory, lstat 'C:'

Error: EISDIR: illegal operation on a directory, lstat 'C:'
    at Object.realpathSync (node:fs:2729:25)
    at toRealPath (node:internal/modules/helpers:65:13)
    at tryFile (node:internal/modules/cjs/loader:589:10)
    at tryPackage (node:internal/modules/cjs/loader:548:16)
    at Module._findPath (node:internal/modules/cjs/loader:823:18)
    at Module._resolveFilename (node:internal/modules/cjs/loader:1487:27)
```

This is a regression introduced in 22.7 and still present on 23.1.1. It
makes Nx Cloud effectively unusable on Windows for anyone working in a
worktree. The only workarounds are `NX_NO_CLOUD=true` or setting
`NX_CACHE_DIRECTORY` by hand.

### Root cause

22.7 added worktree-shared caching, which routed `cacheDir` through
`getMainWorktreeRoot`. That function resolves the main repo root with
Rust's `std::fs::canonicalize` — which on Windows **always** returns a
verbatim (extended-length) path:

```
\\?\C:\Users\Viktor\work\nx_test\nx_issue_demo\org
```

`path.win32.join` treats the device namespace as opaque and preserves
that prefix, so it survives all the way down:

```
get_main_worktree_root  ->  cacheDir  ->  join(cacheDir, 'cloud')  ->  require(fullPath)
     (worktree.rs)         (cache-directory.ts)   (nx-cloud/update-manager.ts:185)   (:98, :114, :139, :155)
```

Node's CJS resolver does not recognize the drive boundary in `\\?\C:\`,
so `Module._nodeModulePaths` walks *past* the filesystem root and ends
up calling `realpathSync('C:')` — hence `EISDIR`.

The underlying defect is a long-standing Node bug
([nodejs/node#60438](nodejs/node#60438), open
for months; see also
[#60435](nodejs/node#60435),
[#61165](nodejs/node#61165)). Rather than wait
on it, Nx should stop emitting the path form Node mishandles.

## Expected Behavior

Targets run normally in a worktree with Nx Cloud enabled.

`get_main_worktree_root` now canonicalizes via
[`dunce`](https://lib.rs/crates/dunce) instead of `std`:

```rust
let abs_path = dunce::canonicalize(&abs_path).unwrap_or(abs_path);
```

`dunce` was already a dependency of the crate and already used elsewhere
in the native code (`watch_filterer.rs`), so this adds nothing new to
the dependency tree.

Importantly, this is **not** a blind prefix strip. `dunce` removes the
`\\?\` prefix only when the result is still a valid Win32 path, and
deliberately keeps it otherwise — so `\\?\UNC\server\share` is not
mangled into a broken relative path, and paths that genuinely require
the prefix are left intact.

This also adds the first tests for `resolve_main_worktree_root`, which
previously had none: one covering the main-repo case, one covering
resolution from a linked worktree plus the absence of a verbatim prefix.

### Notes for reviewers

Two limitations worth knowing, both deliberate:

1. **`dunce` keeps the prefix when the path exceeds 260 characters or
contains a reserved DOS name** (`CON`, `NUL`, `COM1`…). Those workspaces
will still hit this crash. There is no clean fix on the Nx side — such a
path genuinely needs the prefix to work with Win32 APIs — so the
residual case is left to the upstream Node fix.

2. **The new tests cannot fail in CI.** `dunce::canonicalize` is
identical to `std::fs::canonicalize` on non-Windows, and `test-native`
(`cargo test`, reached via `test` → `dependsOn: ["test-native", ...]`)
runs on Linux only — there is no Windows Rust job. I verified this by
reverting the fix locally and confirming the tests still passed on
macOS. They add real first-time coverage of worktree resolution and
document the constraint, but they are not a regression guard for this
platform. **A Windows canary would be valuable before merge** — several
people on the issue offered to test a fix.

## Related Issue(s)

Fixes #35637

<!-- polygraph-session-start -->
---
<p><a
href="https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-Windows-UNC-path-crash-in-Nx-Cloud-worktrees-nx35637-ac64aa6c">View
Polygraph session ↗</a></p>
<!-- polygraph-session-end -->

(cherry picked from commit 385957e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nx does not run targets in git workspace on windows if cloud is enabled

3 participants