Repository navigation
fix(core): resolve main worktree root without a Windows verbatim prefix - #36649
Merged
Merged
Conversation
- 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
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
View your CI Pipeline Execution ↗ for commit 1168a29
☁️ Nx Cloud last updated this comment at |
FrozenPandaz
approved these changes
Aug 12, 2026
|
@AgentEnder Thank you so much for looking into this issue. This has been a hard blocker for us. |
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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=trueor settingNX_CACHE_DIRECTORYby hand.Root cause
22.7 added worktree-shared caching, which routed
cacheDirthroughgetMainWorktreeRoot. That function resolves the main repo root with Rust'sstd::fs::canonicalize— which on Windows always returns a verbatim (extended-length) path:path.win32.jointreats the device namespace as opaque and preserves that prefix, so it survives all the way down:Node's CJS resolver does not recognize the drive boundary in
\\?\C:\, soModule._nodeModulePathswalks past the filesystem root and ends up callingrealpathSync('C:')— henceEISDIR.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_rootnow canonicalizes viadunceinstead ofstd:duncewas 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.
dunceremoves the\\?\prefix only when the result is still a valid Win32 path, and deliberately keeps it otherwise — so\\?\UNC\server\shareis 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:
duncekeeps 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.The new tests cannot fail in CI.
dunce::canonicalizeis identical tostd::fs::canonicalizeon non-Windows, andtest-native(cargo test, reached viatest→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 ↗