Turbopack: outputFileTracingIncludes glob walk hits ELOOP on pnpm workspaces with nested packages depending on their ancestor
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.5k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/Stanzilla/turbopack-symlink-cycle-repro
To Reproduce
pnpm installpnpm --filter app exec next build
The workspace layout that triggers it:
pnpm-workspace.yaml packages: [app, packages/*, packages/umbrella/*]
app/ Next.js app, output: standalone, depends on `shared`
next.config.mjs outputFileTracingRoot: workspace root
outputFileTracingIncludes: { '/*': ['included-asset.txt'] }
packages/umbrella/ package `umbrella`
packages/umbrella/shared/ package `shared`, devDependencies: { umbrella: workspace:* } <-- depends on its physical ancestor
packages/umbrella/leagues/ package `leagues`, dependencies: { shared: workspace:* }
Both conditions are necessary:
- at least one
outputFileTracingIncludesentry (any pattern), and - a workspace package that is physically nested inside another workspace package and declares that ancestor as a dependency or devDependency.
The nested-ancestor dependency is legal in pnpm workspaces. pnpm plants packages/umbrella/shared/node_modules/umbrella -> ../.., which makes the directory tree self-embedding: .../shared/node_modules/umbrella/leagues/node_modules/shared/node_modules/umbrella/... repeats forever.
Current vs. Expected behavior
Current: the build crashes with a TurbopackInternalError:
Error [TurbopackInternalError]: Failed to write app endpoint /page
Caused by:
- reading dir ".../app/node_modules/shared/node_modules/umbrella/leagues/node_modules/shared/node_modules/umbrella/leagues/[...repeats...]/node_modules/shared"
- Too many levels of symbolic links (os error 62)
Debug info:
- Execution of *NftJsonAsset::new failed
- Execution of trace_endpoint failed
- Execution of read_glob failed
- Execution of read_glob_inner failed (repeated ~90 times)
- Execution of read_dir failed
- Execution of <DiskFileSystem as FileSystem>::raw_read_dir failed
- Too many levels of symbolic links (os error 62)
(os error 40 on Linux.)
Expected: the build completes. The include-glob walk should detect the symlink cycle and skip it, the same way most file walkers do.
Analysis
Any outputFileTracingIncludes entry makes get_glob_includes in crates/next-api/src/nft.rs call project_root_path.read_glob(...), which walks the entire tracing root because the glob is compiled with GlobOptions { contains: true, ... }.
The cycle detection in resolve_symlink_safely() in turbopack/crates/turbo-tasks-fs/src/read_glob.rs only detects a symlink whose target is an ancestor of the symlink's own virtual path. In this repro the walk enters the cycle through app/node_modules/shared (or the hoisted node_modules/.pnpm/node_modules/shared), so the virtual path is app/node_modules/shared/node_modules/umbrella/... and neither symlink target (packages/umbrella/shared, packages/umbrella) is ever an ancestor of the virtual path. The check never fires and the walk recurses until the OS returns ELOOP.
Two related problems:
- Multi-symlink cycles evade
resolve_symlink_safely()entirely (this repro). - Even when the ancestor check does fire, it
bail!s and fails the whole build ("'{...}' is a symlink causes that causes an infinite loop!") instead of skipping the cyclic entry.
outputFileTracingExcludes cannot work around this: in nft_json.rs the exclude glob filters traced assets in a separate code path and is never applied to the include-glob directory walk.
Real-world impact: @sentry/nextjs >= 10.70 unconditionally merges its own entries into outputFileTracingIncludes (getsentry/sentry-javascript#23055), so any pnpm monorepo with this (legal) nested-workspace layout now fails to build after a routine Sentry upgrade, with no config escape hatch. We hit this in production on a ~220-package monorepo.
Reproduced on 16.3.0, 16.3.1, and 16.3.1-canary.22. read_glob.rs on canary is byte-identical to 16.3.0, so the latest canary is affected as well.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0
Available memory (MB): 36864
Available CPU cores: 12
Binaries:
Node: 24.19.0
npm: 11.17.0
Yarn: N/A
pnpm: 11.22.0
Relevant Packages:
next: 16.3.1
eslint-config-next: N/A
react: 19.2.3
react-dom: 19.2.3
typescript: N/A
Next.js Config:
output: standalone
Which area(s) are affected? (Select all that apply)
Turbopack, Output (export/standalone)
Which stage(s) are affected? (Select all that apply)
next build (local), Other (CI)
Additional context
A panic log URL is emitted (https://bugs.nextjs.org/...category=turbopack-error-report...), but this is a deterministic logic gap rather than a transient panic, hence the full report with a standalone reproduction.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with crates/next-api/src/nft.rs and turbopack/crates/turbo-tasks-fs/src/read_glob.rs, then run the linked reproduction with pnpm install and pnpm --filter app exec next build. Trace the include-glob walk and symlink handling. Done means the nested pnpm workspace build completes without ELOOP and cyclic entries are skipped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, rust
- Domain
- build-system, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 64/100