Skip to content

Fix build crash when outputFileTracingIncludes matches a symlink - #96999

Draft
sokra wants to merge 1 commit into
vercel:canaryfrom
sokra:fix/nft-symlink-include
Draft

Fix build crash when outputFileTracingIncludes matches a symlink#96999
sokra wants to merge 1 commit into
vercel:canaryfrom
sokra:fix/nft-symlink-include

Conversation

@sokra

@sokra sokra commented Aug 9, 2026

Copy link
Copy Markdown
Member

What?

A glob in outputFileTracingIncludes that wholly matches a symlink pointing at a directory made Turbopack builds fail with a fatal internal error instead of completing:

Error [TurbopackInternalError]: reading file ".../.claude/skills/ink"
Caused by: Is a directory (os error 21)

Included symlinks are now hashed by their link content (target + link type) instead of the content of whatever they point at, and they stay in the trace as the symlink itself rather than being resolved or expanded. Include entries that have no content to hash (a path that disappeared, a symlink escaping the project root, an unreadable file) are now reported as a build issue and skipped instead of aborting the whole build.

Why?

get_glob_includes already returned symlinks (including ones pointing at a directory) as trace entries, but NftJsonAsset hashed them by reading through the link, which fails outright on a directory target. Reading through the link was also the wrong operation in general: copyTracedFiles recreates a traced symlink as a symlink (fs.readlink + fs.symlink), never by copying the resolved file's bytes, so the link's own content is what should be hashed.

How?

  • Added LinkContent::hash in turbo-tasks-fs, mirroring the existing FileContent::hash.
  • Added hash_traced_include in next-api, dispatching by entry type: file → content hash, symlink → link hash, directory → skip (existing behavior, unchanged), anything else/unreadable → emit a new UnhashableTracedFileIssue warning and skip.
  • Added a Rust unit test covering a plain file, a symlink to a file, a symlink to a directory (the regression), a dangling symlink, a symlink escaping the filesystem root, a directory, and a missing path.
  • Extended the build-trace-extra-entries-turbo e2e fixture with a symlink-to-directory match and asserted it shows up in the .nft.json.

Test plan

  • cargo test -p next-api / -p turbo-tasks-fs
  • cargo clippy --all-targets / cargo fmt --check on the touched crates
  • test/production/build-trace-extra-entries-turbo e2e, both Turbopack and webpack modes

A glob in `outputFileTracingIncludes` that wholly matches a symlink pointing
at a directory made the build fail with a fatal internal error:

    Error [TurbopackInternalError]: reading file ".../.claude/skills/ink"
    Caused by: Is a directory (os error 21)

`read_glob` reports a symlink (also one that points at a directory) as
`DirectoryEntry::Symlink`, so `get_glob_includes` collected it as a file and
`NftJsonAsset` then hashed it with `path.read()`, which follows the link and
hits `EISDIR`.

Symlinks are now hashed by their link content (target + link type) instead of
the content of the file they point at. That is also the correct hash: consumers
of the nft json re-create a symlink entry as a symlink (see `copyTracedFiles`)
rather than copying the resolved file. The symlink itself stays in the trace,
it is neither expanded nor resolved.

Include entries that have no content to hash (a path that disappeared, a
symlink escaping the filesystem root, an unreadable file) are now reported as a
warning issue and skipped, instead of aborting the whole build with an internal
error.

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 488ce12

@github-actions

github-actions Bot commented Aug 9, 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
Cold (Listen) 1.065s 1.064s
Cold (Ready in log) 1.063s 1.051s
Cold (First Request) 1.744s 1.720s
Warm (Listen) 1.065s 1.064s
Warm (Ready in log) 1.068s 1.056s
Warm (First Request) 1.722s 1.734s

⚡ Production Builds

Metric Canary PR Change
Fresh Build 7.945s 7.967s
Cached Build 4.044s 4.031s
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change
node_modules Size 553 MB 553 MB
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
00vs_o_mu20cj.js gzip 157 B N/A -
050icza-xjz0i.js gzip 5.73 kB N/A -
06bbynx6pme58.js gzip 65.6 kB N/A -
07jdby0ue616s.js gzip 450 B N/A -
09p7vxghl2k-j.js gzip 71.5 kB N/A -
0anzicvq0iwy6.js gzip 46.7 kB N/A -
0bjdc8muo74n5.js gzip 8.71 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0q9myc6hpg463.js gzip 159 B N/A -
0rci1f3or1a19.js gzip 13.3 kB N/A -
0wi6-onlatv0z.js gzip 157 B N/A -
1_2x714--ii1i.js gzip 8.76 kB N/A -
1-3y752pkth5-.js gzip 10 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1hy0e5cihni_x.js gzip 7.54 kB N/A -
1ob24znun3v3f.js gzip 156 B N/A -
1tf1phijqlx9j.js gzip 220 B 220 B
1uzabyd1120a1.js gzip 8.71 kB N/A -
2-ufv8lc-g7gg.js gzip 10.6 kB N/A -
21kmjy_10x14f.js gzip 8.81 kB N/A -
22alyybqz5_09.js gzip 3.57 kB N/A -
22nqfrqbvmbpc.js gzip 162 B N/A -
28dhc6t85q1_p.js gzip 8.78 kB N/A -
2b8oex9sn1lty.js gzip 158 B N/A -
2f-ilvczue-tp.js gzip 9.46 kB N/A -
2f1u17u5c8iny.js gzip 8.79 kB N/A -
2ikltg_8iegxw.js gzip 10.3 kB N/A -
2jnw_mbvnay89.js gzip 157 B N/A -
2vxi673cz1-t4.js gzip 8.79 kB N/A -
2xll-xpdajhz0.js gzip 169 B N/A -
3_vkwcvy6c1on.js gzip 161 B N/A -
35v9ti6hzvb3d.js gzip 154 B N/A -
38-q43pzktqhs.js gzip 1.46 kB N/A -
3as3skuap9kll.js gzip 157 B N/A -
3mrwxzed0ylgv.js gzip 13.1 kB N/A -
3nbojhxiy1qv_.js gzip 13.7 kB N/A -
3xx08ukqns_6j.js gzip 156 B N/A -
41tg7ge9wtmne.js gzip 157 B N/A -
41u5s3oe2-erp.js gzip 2.29 kB N/A -
445s_9hf8o7ao.js gzip 8.76 kB N/A -
turbopack-1l..-5yv.js gzip 3.73 kB 3.73 kB
0-6bcj16ji2wf.js gzip N/A 10.6 kB -
01eyb-qjob6ki.js gzip N/A 155 B -
02nfvgw02u1hm.js gzip N/A 154 B -
03xplb0u3io-8.js gzip N/A 154 B -
07ryk0jced-sc.js gzip N/A 8.78 kB -
0f_3efx7usi39.js gzip N/A 157 B -
0j64ls9v56hnd.js gzip N/A 3.56 kB -
0jwho9fkrb_t3.js gzip N/A 2.29 kB -
0qumh_ssc14hn.js gzip N/A 71.5 kB -
0roh390ijzxa5.js gzip N/A 8.75 kB -
0s7ldmg9glcmx.js gzip N/A 46.7 kB -
0vex9w55ursqj.js gzip N/A 8.79 kB -
139pc8f53wwpv.js gzip N/A 155 B -
171fdo7-vntgx.js gzip N/A 168 B -
17oe55cu76cd7.js gzip N/A 450 B -
1drww5xikb-c-.js gzip N/A 9.46 kB -
1g60xde_dv17t.js gzip N/A 8.79 kB -
1uzv47btzam64.js gzip N/A 5.73 kB -
1vy7n7wxv_rh5.js gzip N/A 8.81 kB -
2-0i7pl900-ou.js gzip N/A 8.71 kB -
2-kcbngm7ik7y.js gzip N/A 8.75 kB -
253yej48lw0y2.js gzip N/A 154 B -
27r68x9xw5m1c.js gzip N/A 158 B -
29p5-xa4jmdhg.js gzip N/A 13.7 kB -
2jwfwux5a6-99.js gzip N/A 161 B -
2kf6m69v6n3sf.js gzip N/A 155 B -
2lpk5_hknut8q.js gzip N/A 13.1 kB -
2ugc69z0t0ypz.js gzip N/A 1.46 kB -
35v-knu9va3iz.js gzip N/A 150 B -
36dn8i-_3dnq5.js gzip N/A 10 kB -
3enroerykvo9c.js gzip N/A 155 B -
3fljpmwcjxqhx.js gzip N/A 10.3 kB -
3gz44skqdlsrb.js gzip N/A 8.71 kB -
3oslhuq9luvp2.js gzip N/A 7.55 kB -
3w7qb6q5gmba_.js gzip N/A 65.6 kB -
3y9tus7kb5su0.js gzip N/A 13.3 kB -
41eqd-1bupn1g.js gzip N/A 156 B -
Total 401 kB 401 kB ✅ -13 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 1.06 kB 1.06 kB
Total 1.06 kB 1.06 kB
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 882 B 877 B
Total 882 B 877 B ✅ -5 B
Build Cache
Canary PR Change
00000001.sst gzip 13 MB 14.2 MB 🔴 +1.13 MB (+9%)
00000002.sst gzip 14.3 MB 14.4 MB 🔴 +74.3 kB (+1%)
00000003.sst gzip 14.3 MB 13 MB 🟢 1.31 MB (-9%)
00000004.sst gzip 13.4 MB 13.5 MB 🔴 +64.2 kB (+0%)
00000005.sst gzip 10.2 MB 10.2 MB 🔴 +13.6 kB (+0%)
00000006.sst gzip 2.81 MB 2.81 MB
00000007.sst gzip 59 B 59 B
00000008.meta gzip 89 B 298 kB 🔴 +298 kB (+334642%)
00000009.meta gzip 298 kB 89 B 🟢 298 kB (-100%)
00000010.meta gzip 298 kB 298 kB
00000011.meta gzip 298 kB 298 kB
00000012.sst gzip 51.1 kB 52.1 kB 🔴 +1.01 kB (+2%)
00000013.sst gzip 1.83 MB 1.85 MB 🔴 +21.1 kB (+1%)
00000014.sst gzip 59 B 59 B
00000015.meta gzip 116 B 116 B
00000016.meta gzip 325 kB 325 kB
00000017.meta gzip 405 kB 404 kB
00000018.sst gzip 51.9 kB 1.4 MB 🔴 +1.34 MB (+2588%)
00000019.sst gzip 1.37 MB 52.9 kB 🟢 1.31 MB (-96%)
00000020.sst gzip 59 B 59 B
00000021.meta gzip 116 B 116 B
00000022.meta gzip 325 kB 325 kB
00000023.meta gzip 371 kB 371 kB
00000024.sst gzip 51.9 kB 53.1 kB 🔴 +1.14 kB (+2%)
00000025.sst gzip 1.37 MB 1.39 MB 🔴 +29.6 kB (+2%)
00000026.sst gzip 59 B 59 B
00000027.meta gzip 116 B 116 B
00000028.meta gzip 325 kB 325 kB
00000029.meta gzip 371 kB 371 kB
00000030.sst gzip 51.3 kB 52.1 kB 🔴 +753 B (+1%)
00000031.sst gzip 1.37 MB 1.39 MB 🔴 +29.7 kB (+2%)
00000032.sst gzip 59 B 59 B
00000033.meta gzip 116 B 116 B
00000034.meta gzip 325 kB 325 kB
00000035.meta gzip 371 kB 371 kB
00000036.sst gzip 52 kB 52.9 kB 🔴 +949 B (+2%)
00000037.sst gzip 1.37 MB 1.39 MB 🔴 +29.7 kB (+2%)
00000038.sst gzip 59 B 59 B
00000039.meta gzip 116 B 116 B
00000040.meta gzip 325 kB 325 kB
00000041.meta gzip 371 kB 371 kB
CURRENT gzip 24 B 24 B
LOG gzip 678 B 678 B
Total 80.1 MB 80.2 MB ⚠️ +115 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 375 kB 375 kB
app-page-exp..prod.js gzip 207 kB 207 kB
app-page-tur...dev.js gzip 374 kB 374 kB
app-page-tur..prod.js gzip 207 kB 207 kB
app-page-tur...dev.js gzip 371 kB 371 kB
app-page-tur..prod.js gzip 205 kB 205 kB
app-page.run...dev.js gzip 371 kB 371 kB
app-page.run..prod.js gzip 205 kB 205 kB
app-route-ex...dev.js gzip 83.3 kB 83.3 kB
app-route-ex..prod.js gzip 56.4 kB 56.4 kB
app-route-tu...dev.js gzip 83.3 kB 83.3 kB
app-route-tu..prod.js gzip 56.4 kB 56.4 kB
app-route-tu...dev.js gzip 82.9 kB 82.9 kB
app-route-tu..prod.js gzip 56.2 kB 56.2 kB
app-route.ru...dev.js gzip 82.8 kB 82.8 kB
app-route.ru..prod.js gzip 56.2 kB 56.2 kB
dev-validati...dev.js gzip 133 kB 133 kB
dev-validati...dev.js gzip 133 kB 133 kB
dev-validati...dev.js gzip 131 kB 131 kB
dev-validati...dev.js gzip 131 kB 131 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 46 kB 46 kB
pages-api-tu..prod.js gzip 34.3 kB 34.3 kB
pages-api.ru...dev.js gzip 46 kB 46 kB
pages-api.ru..prod.js gzip 34.3 kB 34.3 kB
pages-turbo....dev.js gzip 54.8 kB 54.8 kB
pages-turbo...prod.js gzip 39.9 kB 39.9 kB
pages.runtim...dev.js gzip 54.8 kB 54.8 kB
pages.runtim..prod.js gzip 39.9 kB 39.9 kB
server.runti..prod.js gzip 67 kB 67 kB
use-cache-pr...dev.js gzip 72.3 kB 72.3 kB
use-cache-pr...dev.js gzip 72.4 kB 72.4 kB
use-cache-pr...dev.js gzip 70.6 kB 70.6 kB
use-cache-pr...dev.js gzip 70.6 kB 70.6 kB
Total 4.1 MB 4.1 MB ⚠️ +2 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/488ce12594612ceec7a6b1bffc2fdad6577ddc55/next

Commit: 488ce12

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.

1 participant