vercel / vercel/next.js

[next-rspack] `next dev` aborts with a Rust panic when the error overlay or browser-log forwarding source-maps a frame during a rebuild

Open
#98,213 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Rspack
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://github.com/Dwlad90/next-rspack-stale-compilation-repro

To Reproduce
npm install
npm run generate   # 5000 tiny modules so a rebuild takes long enough to be hit
npm run repro      # boots `next dev -p 3456`, edits a module, posts stack frames, repeats

The driver posts to POST /__nextjs_original-stack-frames (what the error overlay does) while
a rebuild is in flight. No browser is needed. Result on my machine:

Two runs from a clean .next: REPRODUCED after 1 trial(s) both times, attempted to read from stolen value: ...BuildModuleGraphArtifact. Control with the stack-frame posting switched off (POSTERS=0): green through 8 trials with the same edits, so the source-map request is load-bearing, not the edit alone.

Without the driver: next dev, open the page in a browser whose console shows an error (any
console.error in a client component), then append a comment to an imported file every
second. Either the overlay's stack-frame request or the forwarded console error makes the
server source-map a stack during the rebuild.

Current vs. Expected behavior

Current: the dev server dies and has to be restarted.

Panic occurred at runtime. Please file an issue on GitHub with the panic info and backtrace below: https://github.com/web-infra-dev/rspack/issues

thread '<unnamed>' panicked at /Users/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rspack_core-0.100.4/src/utils/steal_cell.rs:60:26:
attempted to read from stolen value: rspack_core::artifacts::module_ids_artifact::ModuleIdsArtifact
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The stolen value is sometimes BuildModuleGraphArtifact instead.

Expected: a stack frame that cannot be mapped during a rebuild comes back unmapped. The dev
server keeps running.

Provide environment information
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin 25.6.0
Binaries:
  Node: 24.11.0
  npm: 11.x
  pnpm: 11.x
Relevant Packages:
  next: 16.3.3 (also 16.3.4 and 16.4.0-canary.15 pin the same @next/rspack-core 1.0.3)
  next-rspack: 16.3.3
  @next/rspack-core: 1.0.3 (@next/rspack-binding 1.0.3, rspack_core 0.100.4)
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 7.0.2
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

Developer Experience, Rspack

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

next dev (local)

Additional context
Cause

packages/next/src/server/dev/middleware-webpack.ts reads Stats.compilation from request
handlers, outside the compile lifecycle:

  • getOverlayMiddlewarePOST /__nextjs_original-stack-framesgetOriginalStackFrames
  • browser-logs/receive-logs.tsgetSourceMappedStackFramesmapFramesUsingBundler
    getOriginalStackFrames

Both reach getSourceMapFromCompilationgetModuleById, which does compilation.modules
and then chunkGraph.getModuleId(module) for every module until one matches.

Under webpack that is fine: a kept Stats.compilation is a JavaScript snapshot. Under rspack it
is not. The binding hands JS a raw pointer to Compiler.compilation, and Compiler::rebuild
replaces that struct in place (std::mem::replace), so the kept object aliases the compilation
being rebuilt on tokio threads. compilation.modules dereferences rspack's
StealCell<BuildModuleGraphArtifact> (stolen during make) and getModuleId dereferences
StealCell<ModuleIdsArtifact> (stolen during the module-ids hooks). Whichever pass the rebuild
is in is the artifact that panics, and the binary aborts.

The rspack side is filed separately (web-infra-dev/rspack#15451): the stale handle should fail
closed. This issue is about the host side, which is independently fixable and would also stop
the abort today.

Evidence

I wrapped every native binding accessor from JS and logged any call made while a compiler was
between watchRun and done with no compile hook on the stack. The last line before every
panic was getModuleId or compilation.modules from getModuleById, via one of the two routes
above. In one window between a client thisCompilation and the panic there were 1,096,130
getModuleId calls from that scan. Making the stale Compilation object throw kept the same
loop green through 41 trials; the baseline panicked at trials 1, 6 and 18.

Two smaller problems on the same path
  1. logging.browserToTerminal: false does not stop the forwarder from source-mapping. handleLog
    still processes formatted-error / any-logged-error entries whenever the file logger is
    enabled, and the file logger is enabled by experimental.mcpServer, which defaults to true.
    So a project that has switched browser logs off still runs this code on every browser error.
  2. getModuleById is a linear scan with one native call per module per frame. With a few
    thousand modules and a 40-frame stack that is well over 100k native calls for one request.
Fix that works for us

Skip the lookup while the owning compiler is rebuilding. The scan is synchronous and rebuilds
only start from JavaScript, so the check is not racy:

function getModuleById(id: string | undefined, compilation: webpack.Compilation) {
  // Under rspack a compilation kept from an earlier build aliases the one being rebuilt.
  if (compilation.compiler?.watching?.running === true) return undefined
  const { chunkGraph, modules } = compilation
  return [...modules].find((module) => chunkGraph.getModuleId(module) === id)
}

Both rspack's and webpack's Watching expose running. We carry this as a pnpm patch and
the panic has not recurred; a frame requested mid-rebuild comes back unmapped and maps on the
next request.

packages/next/src/server/dev/middleware-webpack.ts on canary (2026-09-03) still has the
unguarded getModuleById.

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 in packages/next/src/server/dev/middleware-webpack.ts and trace getModuleById through getOriginalStackFrames, including the overlay and browser-log routes. Run the linked reproduction with npm install, npm run generate, and npm run repro while inspecting the existing source-map behavior. Done means mid-rebuild frames return unmapped instead of aborting next dev, and later requests still map normally.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nextjs, node.js
Domain
build-system, developer-experience, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.