Skip to content

Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows#95668

Merged
bgw merged 2 commits into
canaryfrom
bgw/canonicalize-root-path
Jul 21, 2026
Merged

Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows#95668
bgw merged 2 commits into
canaryfrom
bgw/canonicalize-root-path

Conversation

@bgw

@bgw bgw commented Jul 10, 2026

Copy link
Copy Markdown
Member

I was seeing failures on #95628 and went down this rabbit hole.

  • We were (incorrectly) not canonicalizing the DiskFileSystem root dir. This happened to work out for us because pnpm also wasn't canonicalizing junction point targets, and so the root dir prefix matched. However after #95628, pnpm started canonicalizing junction point targets because we added pnpm-workspace.yaml files. This was just coincidence and indicative of much deeper problems.

  • dunce isn't a good fit for us. It picks a win32 or verbatim representation based on path length, which would mean that long paths inside of a short root path would never work because we'd never be able to strip the prefix, since we'd be looking for a win32 path prefix. Omnipath is better for what we want to do: https://docs.rs/omnipath/latest/omnipath/windows/trait.WinPathExt.html

  • The windows verbatim path format works with long paths (>260 characters) and behaves a lot closer to cross-platform unix paths, which is what we want. E.g. if somebody tries to import a JS module from a Windows 8.3 short path, it should fail. This will fail with verbatim paths (good!). This PR always uses the verbatim path format internally within DiskFileSystem.

  • read_link was re-inventing try_from_sys_path but badly. Just call try_from_sys_path.

  • turbopack/crates/turbo-tasks-fs/src/embed/file.rs was dead code.

  • validate_path_length_inner was incorrectly referring to verbatim paths as UNC paths. These are two different things.

  • Canonicalize any absolute symlink targets outside of the DiskFileSystem root path, resolving any symlinks they may have, normalizing case-insensitive paths, and handling Windows 8.3 short name format.

Some Context About Windows Path Formats:

  • win32 paths: These are your normal user-friendly C:\foo\blah paths. When calling most filesystem APIs, these are limited to 260 characters unless the user has enabled long paths on the system, and avoiding the limit with these paths also requires a longPathAware application manifest. You can mix / and \ in win32 paths, and Windows will normalize the path separator for you.

  • verbatim paths: This is a rust-specific name (used by the stdlib) for the "extended length" prefixed paths. These are paths starting with \\?\. These paths can be up to ~32,767 characters long. They do not support Windows 8.3 shortnames. They do not normalize between / and \, and do not support . or .. components, however Path::join will do this normalization if given a verbatim path.

  • Windows 8.3 shortnames: A legacy format where paths can be represented by 8 characters, followed by a tilde and a digit (or hash in some extreme cases where there are many collisions), followed by a 3 character file extension. NTFS supports this, ReFS (used by Windows Dev Drive) does not.

  • UNC ("Universal Naming Convention"): This is a specific representation of paths used for remote files on network servers.

  • file:// URIs: These must be formed from win32 style paths, though the normal 260 character limit typically does not apply here.

  • canonicalize: Corresponds to std::fs::canonicalize. This involves actual fs operations (that we can't easily track), and recursively resolves symlinks. It will fail if the file path does not exist. On windows, this always returns a verbatim path.

  • wide format: Windows paths are utf-16. Generally we don't support paths that can't be encoded to utf-8, but we should try to fail as obviously as we can in these cases. We don't have good filesystem issue handling yet though.

  • case sensitivity: Generally paths are not case-sensitive on Windows or macOS, but are case-sensitive on most Linux filesystems. The exact definition of how case sensitivity works with unicode paths is highly platform and filesystem dependent.

  • junction points: The windows equivalent of symlinks (symlinks are also supported, but require developer mode to be turned on). These can contain an arbitrary path reference in any format (win32, verbatim, etc) with any case and with windows 8.3 short names. Junction points are always target absolute paths to directories.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

Warning

No stats were collected for Webpack because its stats job did not complete (it failed, was cancelled, or timed out). The results below only cover the bundlers that finished.

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 810ms 811ms ▂▂▂▇▂
Cold (Ready in log) 773ms 775ms ▁▂▁█▁
Cold (First Request) 1.286s 1.281s ▂▃▂█▁
Warm (Listen) 761ms 762ms ▁▁▁▇▁
Warm (Ready in log) 764ms 762ms ▁▂▁▇▁
Warm (First Request) 1.289s 1.275s ▂▂▁▇▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.953s 4.933s ▂▄▁█▁
Cached Build 5.123s 5.026s ▃▃▃▇▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
node_modules Size 527 MB 527 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
03frh1tm1pkvs.js gzip 65.6 kB N/A -
0a-p0meqxynjz.js gzip 10.3 kB N/A -
0avtke4yefj4-.js gzip 13.1 kB N/A -
0ban--bx2qns6.js gzip 155 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0ke29q58efn1a.js gzip 156 B N/A -
0m3evdsbel0p0.js gzip 153 B N/A -
0p6ivy9lb7-ut.js gzip 157 B N/A -
0pt5of3ch6d2u.js gzip 13.6 kB N/A -
0sug3ihj23mvn.js gzip 154 B N/A -
0xnsdo-6y9r3k.js gzip 5.72 kB N/A -
0y25v1ej71uft.js gzip 155 B N/A -
0yi7is8u4covm.js gzip 8.77 kB N/A -
12zd-d7xicsb4.js gzip 158 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1g2ea05dkmmwi.js gzip 45.2 kB N/A -
1mqxdmso4160h.js gzip 160 B N/A -
1prv-wg6jgtt2.js gzip 8.77 kB N/A -
1t47vod02e_4a.js gzip 8.75 kB N/A -
1tf1phijqlx9j.js gzip 220 B 220 B
1tw002sq5bo4l.js gzip 10 kB N/A -
1wlerik1n7zbr.js gzip 8.7 kB N/A -
2d91wcgx6lf2k.js gzip 153 B N/A -
2koflviradwt3.js gzip 7.4 kB N/A -
2lviwqiqxm904.js gzip 3.52 kB N/A -
2ocpv0tylryvn.js gzip 160 B N/A -
2pevdqjpbf3xk.js gzip 9.45 kB N/A -
2qd2anu9eizlz.js gzip 8.75 kB N/A -
2sv39ae5_8vfj.js gzip 8.81 kB N/A -
2utx6n7w65xqa.js gzip 8.7 kB N/A -
2xsvwiy3-s105.js gzip 156 B N/A -
2zhsfsomo6g1v.js gzip 169 B N/A -
3-ukivglqkfeq.js gzip 8.78 kB N/A -
325xisqrylugu.js gzip 450 B N/A -
3jc9ghvds1spf.js gzip 2.29 kB N/A -
3juwkvkgvywfe.js gzip 10.6 kB N/A -
3kbove7q58uqh.js gzip 13.2 kB N/A -
3qn7vntp30gaj.js gzip 1.47 kB N/A -
3s5fbzg2qit8e.js gzip 156 B N/A -
3xtfvhlgn22av.js gzip 71.1 kB N/A -
turbopack-0z.._-wk.js gzip 3.78 kB N/A -
turbopack-0z..g5ya.js gzip 3.8 kB N/A -
turbopack-10..hlo9.js gzip 3.8 kB N/A -
turbopack-11..yksz.js gzip 3.81 kB N/A -
turbopack-1k..5le3.js gzip 3.8 kB N/A -
turbopack-2p..8e3n.js gzip 3.8 kB N/A -
turbopack-2s..l7od.js gzip 3.8 kB N/A -
turbopack-2u..c9m0.js gzip 3.8 kB N/A -
turbopack-39..3ysu.js gzip 3.8 kB N/A -
turbopack-3i..u_md.js gzip 3.8 kB N/A -
turbopack-3l..z_a8.js gzip 3.81 kB N/A -
turbopack-3w..t926.js gzip 3.8 kB N/A -
turbopack-3y..v370.js gzip 3.82 kB N/A -
turbopack-40..h7ed.js gzip 3.8 kB N/A -
030a1njeh5b77.js gzip N/A 10 kB -
0ceaj3rmii5dl.js gzip N/A 157 B -
0ot0qt5np20h7.js gzip N/A 8.78 kB -
0po7u01wwhl8s.js gzip N/A 157 B -
12m9rr7_7cexd.js gzip N/A 8.7 kB -
15-b7iytymgp5.js gzip N/A 65.6 kB -
186r3y_rfvr0b.js gzip N/A 8.78 kB -
19v97848yrjp7.js gzip N/A 8.81 kB -
1b9oo_n-opj3l.js gzip N/A 156 B -
1mvdo2etbyclo.js gzip N/A 156 B -
1nd_0rwgz2ozk.js gzip N/A 9.45 kB -
1oi9pltr_e10m.js gzip N/A 10.6 kB -
1qow5vb4es1bl.js gzip N/A 152 B -
1rd9tzqgzsz2w.js gzip N/A 8.77 kB -
1rewgj8du8r7b.js gzip N/A 157 B -
1rq1g_cwv642d.js gzip N/A 10.3 kB -
1uojw_uabeg93.js gzip N/A 450 B -
1w-7lusgyl81y.js gzip N/A 8.7 kB -
2-4bvun4utbsg.js gzip N/A 157 B -
20pls2cco-oyl.js gzip N/A 71.1 kB -
21h56wdn9zddo.js gzip N/A 8.75 kB -
21kc-4x8zf_oh.js gzip N/A 155 B -
22nt-vwpfkpq5.js gzip N/A 155 B -
2j9dbydew6g_b.js gzip N/A 162 B -
2lg10omv7xu14.js gzip N/A 3.52 kB -
2ovff1533zvno.js gzip N/A 13.2 kB -
2s4z8jy0z7v93.js gzip N/A 1.46 kB -
2y6gb5bs4mnam.js gzip N/A 8.75 kB -
2zvoj8k1z3cei.js gzip N/A 7.41 kB -
341h71ln1jpl3.js gzip N/A 2.29 kB -
34ttpt_n7605h.js gzip N/A 5.72 kB -
36ckfq9o-ys-a.js gzip N/A 13.1 kB -
37pm_isc9xj9i.js gzip N/A 163 B -
3vdh6_u50pu5t.js gzip N/A 13.6 kB -
3zbnmofunn_bw.js gzip N/A 158 B -
441_xec6sd6zp.js gzip N/A 45.1 kB -
45148p5lps61x.js gzip N/A 170 B -
turbopack-00..m1m7.js gzip N/A 3.81 kB -
turbopack-0d..4kzx.js gzip N/A 3.81 kB -
turbopack-0z..p79s.js gzip N/A 3.81 kB -
turbopack-1c..77uv.js gzip N/A 3.81 kB -
turbopack-1i..3kn6.js gzip N/A 3.81 kB -
turbopack-2d..v5q3.js gzip N/A 3.82 kB -
turbopack-2f..v9c_.js gzip N/A 3.78 kB -
turbopack-2k..d454.js gzip N/A 3.81 kB -
turbopack-2t..j9f5.js gzip N/A 3.81 kB -
turbopack-3_..5w5s.js gzip N/A 3.81 kB -
turbopack-39..bm8d.js gzip N/A 3.81 kB -
turbopack-3e..0dhh.js gzip N/A 3.8 kB -
turbopack-3t..czon.js gzip N/A 3.81 kB -
turbopack-3v..bfyi.js gzip N/A 3.81 kB -
Total 448 kB 448 kB ⚠️ +98 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 778 B 777 B
Total 778 B 777 B ✅ -1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 436 B 435 B
Total 436 B 435 B ✅ -1 B

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 366 kB 366 kB
app-page-exp..prod.js gzip 202 kB 202 kB
app-page-tur...dev.js gzip 365 kB 365 kB
app-page-tur..prod.js gzip 202 kB 202 kB
app-page-tur...dev.js gzip 362 kB 362 kB
app-page-tur..prod.js gzip 200 kB 200 kB
app-page.run...dev.js gzip 362 kB 362 kB
app-page.run..prod.js gzip 200 kB 200 kB
app-route-ex...dev.js gzip 81.7 kB 81.7 kB
app-route-ex..prod.js gzip 55.6 kB 55.6 kB
app-route-tu...dev.js gzip 81.8 kB 81.8 kB
app-route-tu..prod.js gzip 55.6 kB 55.6 kB
app-route-tu...dev.js gzip 81.4 kB 81.4 kB
app-route-tu..prod.js gzip 55.4 kB 55.4 kB
app-route.ru...dev.js gzip 81.3 kB 81.3 kB
app-route.ru..prod.js gzip 55.3 kB 55.3 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 45.4 kB 45.4 kB
pages-api-tu..prod.js gzip 34.1 kB 34.1 kB
pages-api.ru...dev.js gzip 45.4 kB 45.4 kB
pages-api.ru..prod.js gzip 34.1 kB 34.1 kB
pages-turbo....dev.js gzip 54.9 kB 54.9 kB
pages-turbo...prod.js gzip 39.7 kB 39.7 kB
pages.runtim...dev.js gzip 54.8 kB 54.8 kB
pages.runtim..prod.js gzip 39.7 kB 39.7 kB
server.runti..prod.js gzip 67.8 kB 67.8 kB
use-cache-pr...dev.js gzip 71.6 kB 71.6 kB
use-cache-pr...dev.js gzip 71.6 kB 71.6 kB
use-cache-pr...dev.js gzip 69.9 kB 69.9 kB
use-cache-pr...dev.js gzip 69.9 kB 69.9 kB
Total 3.51 MB 3.51 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/0f280ac6b004f39b50f2e8511ff632e9a356403c/next

Commit: 0f280ac

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 0f280ac

@bgw
bgw requested a review from a team July 10, 2026 01:21
@bgw
bgw force-pushed the bgw/canonicalize-root-path branch from 69305e4 to 082cad2 Compare July 20, 2026 07:25
@bgw
bgw changed the base branch from canary to bgw/fs-lock-arc-keys July 20, 2026 07:25
@bgw bgw changed the title Turbopack: Fix missing canonicalization of DiskFileSystem::new constructor call Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows Jul 20, 2026
@bgw
bgw force-pushed the bgw/fs-lock-arc-keys branch from ad90db0 to eeadc40 Compare July 20, 2026 21:10
@bgw
bgw force-pushed the bgw/canonicalize-root-path branch from 082cad2 to 81793a4 Compare July 20, 2026 21:10
@bgw
bgw force-pushed the bgw/fs-lock-arc-keys branch from eeadc40 to a756827 Compare July 20, 2026 21:11
@bgw
bgw force-pushed the bgw/canonicalize-root-path branch 2 times, most recently from a7ec075 to 58f0896 Compare July 20, 2026 22:15
@bgw
bgw requested a review from lukesandberg July 20, 2026 22:33
@bgw
bgw marked this pull request as ready for review July 20, 2026 22:38
@bgw

bgw commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

@lukesandberg this has turned into a very significant refactor of all of our windows path handling since your last review.

Comment thread turbopack/crates/turbo-tasks-fs/src/lib.rs
Comment thread turbopack/crates/turbo-tasks-fs/src/lib.rs Outdated
Comment thread turbopack/crates/turbo-tasks-fs/src/lib.rs
Comment thread crates/next-napi-bindings/src/next_api/project.rs Outdated

Copy link
Copy Markdown
Contributor

unsubscribe

Base automatically changed from bgw/fs-lock-arc-keys to canary July 21, 2026 19:52
@bgw
bgw force-pushed the bgw/canonicalize-root-path branch from 58f0896 to 0409c3a Compare July 21, 2026 19:53
bgw added 2 commits July 21, 2026 12:56
…uctor call and always use verbatim paths internally for Windows
@bgw
bgw force-pushed the bgw/canonicalize-root-path branch from 0409c3a to 0f280ac Compare July 21, 2026 20:21
@bgw
bgw merged commit 3214020 into canary Jul 21, 2026
222 of 225 checks passed
@bgw
bgw deleted the bgw/canonicalize-root-path branch July 21, 2026 21:54
bgw added a commit that referenced this pull request Jul 21, 2026
…endencies (#95628)

We also mitigate the security impact of e2e tests by running everything
in VMs, and by not reading caches in release jobs, but we should still
be using `minimumReleaseAge` in e2e tests.

Ideally we'd also use lockfiles here too, and I'll work on that next,
but that is likely a lot more complicated because we probably need some
way to merge lockfiles, since we can't pin the next.js and react
versions in every test, and we need a reasonable/efficient way to
bulk-update all the lockfiles for tests that need it.

Depends on #95668 for a bunch of
windows path representation fixes that this ends up exposing in CI,
because adding workspace files causes pnpm to change the absolute path
representation used in the Windows junction points that it creates.
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.

2 participants