vercel / vercel/next.js

Turbopack: outputFileTracingIncludes glob walk hits ELOOP on pnpm workspaces with nested packages depending on their ancestor

Open
#97,550 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Output Turbopack
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
  1. pnpm install
  2. pnpm --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 outputFileTracingIncludes entry (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:

  1. Multi-symlink cycles evade resolve_symlink_safely() entirely (this repro).
  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.