Skip to content

Turbopack traces a pnpm hoisted-store alias without its target files, producing a dangling symlink that fails Vercel deploys with ENOENT #98791

Closed

Description

@davidbarratt

Link to the code that reproduces this issue

https://github.com/davidbarratt/nextjs-nft-hoisted-symlink-repro

To Reproduce

git clone https://github.com/davidbarratt/nextjs-nft-hoisted-symlink-repro
cd nextjs-nft-hoisted-symlink-repro
pnpm install
pnpm build
find .next -xtype l

find prints one dangling symlink:

.next/standalone/node_modules/.pnpm/node_modules/scheduler
  -> ../[email protected]/node_modules/scheduler

node check-nft.mjs inspects the *.nft.json traces directly — the same defect
before output: "standalone" materializes it:

alias traced, zero target files: scheduler -> .../.pnpm/[email protected]/node_modules/scheduler

hoisted aliases traced: 47
aliases with no traced target files: 1

The app is one route handler that imports @react-pdf/renderer, plus a layout.

Current vs. Expected behavior

Current: a route's *.nft.json lists pnpm's hoisted-store link
node_modules/.pnpm/node_modules/scheduler as a bare entry, and traces no files
from the copy that link resolves to. Two copies of scheduler are in the store —
0.28.0 from react-dom (what the hoisted link points at) and
0.25.0-rc-603e6108-20241029 from @react-pdf/reconciler (the only one whose
files are traced). The alias is therefore packed empty.

Deploying that output to Vercel fails during extraction, before the app runs:

Building: Extracting deployment files...
Error: ENOENT: no such file or directory, realpath
'/vercel/path0/node_modules/.pnpm/node_modules/scheduler'

Expected: an alias listed in a trace is packed with the files it resolves to,
or is not listed at all. Either way the output contains no symlink without a
target.

Provide environment information

Operating System:
  Platform: linux
  Arch: arm64
Binaries:
  Node: 24.20.0
  pnpm: 11.11.0
Relevant Packages:
  next: 16.4.0-canary.32 (also reproduced on 16.3.5)
  react: 19.3.0
  react-dom: 19.3.0
  typescript: 5.9.3
Next.js Config:
  output: standalone

Which area(s) are affected? (Select all that apply)

Turbopack, Output (export/standalone), Module Resolution

Which stage(s) are affected? (Select all that apply)

next build (local), Vercel (Deployed)

Additional context

Impact. Every deploy of an affected app fails at extraction — there is no
partial or degraded mode, and no retry clears it. I first hit this across ten CI
runs on unrelated branches of a private monorepo, on three separate apps, each
blocked until the output was patched; a second app failed identically on
supports-color, which has four copies in its store. [citation needed] — that
CI is private, so the public evidence for the failure is the reproduction above.

What is and isn't required, each verified by rebuilding with only that
changed:

Changed Result
output: "standalone" removed still 1 bad alias (trace only; find has no tree to scan)
serverExternalPackages removed still 1 bad alias
both removed — empty next.config.mjs still 1 bad alias
overrides: { scheduler: 0.28.0 } 0 bad aliases

So neither config option is implicated. The last row is the mechanism: once the
hoisted link and the traced files agree on a version, the defect is gone.

I mention serverExternalPackages only because the app where I first saw this
listed @react-pdf/renderer there, and
#95816 points at externalized
packages — but removing it here changes nothing, so it is not the trigger.

pnpm's default symlinked linker is required. node-linker=hoisted has no
.pnpm/node_modules directory and cannot hit this.

Workarounds, in preference order:

  1. Dedupe the package so the alias and traced files agree (the table's last row).
    Only viable when one version actually satisfies both dependents.
  2. Delete targetless links from the build output before uploading —
    find .vercel/output -xtype l -delete. A link with no target cannot serve a
    request, so removing it is safe, but it is a patch over the output rather than
    a fix.

Relationship to
#95816.
Same shape — an alias
traced without its store target files — and possibly the same machinery, but a
different alias class and failure point, so I filed separately rather than
commenting there:

#95816 this
Alias Turbopack content-hashed .next/node_modules/<pkg>-<hash> pnpm hoisted store node_modules/.pnpm/node_modules/<pkg>
Package manager bun isolated linker pnpm
Surfaces at runtime, ERR_MODULE_NOT_FOUND deploy, ENOENT on realpath
Trigger shared SSR chunk referencing a package the endpoint does not import two copies in the store; the hoisted link points at the untraced one

This reproduction emits no .next/node_modules/<pkg>-<hash> entries at all, so
it is not the #95816 path. Happy to fold this in as a comment there if you read
them as one bug.

Open questions I could not answer:

  • I have not bisected the first canary that introduced this. It reproduces on
    16.3.5 and 16.4.0-canary.32; I have not tested the 16.2.x line, where
    #95816 reports its own variant absent.
  • In the private app this began failing partway through a day on which neither
    the Next version nor the lockfile changed — the same commit range built clean
    earlier and dangling later. I could not identify what shifted, so I cannot say
    whether emission here is deterministic or depends on chunking.

Activity

  1. added
    Module ResolutionModule resolution (CJS / ESM, module resolving).
    OutputRelated to the the output configuration option.
    TurbopackRelated to Turbopack with Next.js.
    on Sep 17, 2026
  2. Tiancheng-Xu commented on Sep 17, 2026

    @Tiancheng-Xu

    I can investigate this tracing path. I will reproduce the dangling pnpm hoisted-store alias with the linked fixture, trace how the target package files are selected, and add a focused regression test with the smallest fix if confirmed. I will report the validation results in the PR.

  3. vpontis commented on Sep 17, 2026

    @vpontis

    Same failure with Bun's isolated linker, so this is not pnpm-specific.

    Every Vercel deploy of three Next.js apps in our monorepo started failing at 21:45 UTC on Sep 16 with no dependency or config change on our side:

    Error: ENOENT: no such file or directory, realpath '/vercel/path0/node_modules/.bun/node_modules/escape-string-regexp'
    

    Bun hoists escape-string-regexp@4 (eslint's) into node_modules/.bun/node_modules/, while the code path actually traced uses @5 through mdast-util-find-and-replace. The page's .nft.json lists the hoisted link without tracing its target, so the upload has the link and none of the @4 files. Two more packages of the same shape (mime-db, mime-types) surfaced one at a time after the first was excluded.

    Workaround for now: outputFileTracingExcludes for the dangling links. The timing across unrelated projects suggests Vercel's extraction step started realpath-ing every traced entry, and this tracer gap has been there for a while.

  4. bgw commented on Sep 21, 2026

    @bgw
    Member

    Hi everyone. Sorry about the disruption.

    It appears that this corresponds to an internal incident on Vercel involving changes to symlink resolution that affected some number of deployments. The change rolled out on September 16th and was rolled back later that day.

    It's possible that #98798 is still valid regardless of the Vercel issue, I'll take a look at that PR and triage it separately.

  5. roji-tech commented on Sep 23, 2026

    @roji-tech

    A third, independent confirmation of the same duplicate-package mechanism, from an unrelated production monorepo (Next.js 16.2.6, Turbopack, pnpm 11) — and the same trigger package.

    We've had a long-standing, unexplained Vercel deploy failure at the "Extracting deployment files..." step (opaque, no diagnostic text via CLI/dashboard/vercel inspect --logs), on routes that traced @react-pdf/renderer through outputFileTracingIncludes. Two fixes suggested by Vercel's own support tooling were each tested against a real vercel deploy --prebuilt and disproven — including switching to node-linker=hoisted, which flattened the tree with zero broken symlinks by our own lstat verification, yet the deploy still failed with the byte-for-byte identical opaque signature.

    Having now found this issue, we confirmed the same duplicate-version shape in our own pnpm store:

    node_modules/.pnpm/[email protected]                       # react-dom's
    node_modules/.pnpm/[email protected]   # @react-pdf/[email protected]'s own pin
    

    Same trigger package (@react-pdf/renderer → @react-pdf/reconciler pinning its own scheduler) as the original report here. We haven't yet applied/tested the pnpm.overrides dedupe workaround against a real deploy, but the mechanism match is exact enough that it seemed worth adding as a third data point, especially since the fix (#98798) is still open/unmerged — this doesn't look like it was only the Sep 16 Vercel-side incident.

  6. github-actions commented on Oct 7, 2026

    @github-actions
    Contributor

    This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

  7. locked as resolved and limited conversation to collaborators on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Module ResolutionModule resolution (CJS / ESM, module resolving).OutputRelated to the the output configuration option.TurbopackRelated to Turbopack with Next.js.locked

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions