Skip to content

Turbopack miscompilation leading to runtime crash when project contains async modules, import cycles, and an unlucky import-graph shape #85988

@jimrandomh

Description

@jimrandomh

Link to the code that reproduces this issue

https://github.com/jimrandomh/turbopack-issue-repro

To Reproduce

Start a server using the linked reduced repro and yarn dev or yarn build && yarn start, and make a request to http://localhost:3000/api/test.

Current vs. Expected behavior

If a project contains an import cycle, has an import path from the cycle to an async module, and has a certain import-graph topology, then some function calls will fail at runtime with an error like:

(0 , w.exampleFunction) is not a function

This will always be a function call from a file which does not participate in the import cycle, which has imported a function that is in a file which is in the import cycle. This happens because compute_async_module_info_single marked the imported file as an async module, but didn't propagated this to the file that imports it.

compute_async_module_info_single determines which modules are async by (1) propagating async-ness backwards across import relations with a depth-first traversal, then (2) propagating async-ness through import cycles. This is incorrect in that it stops too early; after propagating async-ness through an import cycle, it needs to return to (1), since there may be files that imported something in the cycle that weren't marked.

(Repeating-until-no-changes is definitely correct, here, but might be noticeably slow. There is a fancy graph-alpgorithm solution to this that would be faster, I think.)

In the linked test case, the import graph has the following topology:

 api/test/route.ts
 |   \
 v    v
 A<-  D
 |  \ |
 v   \v
 B--->C
 |
 v
 async

In this test case, D is not marked as an async module, and the function call from D to C fails.

This impacted our project (ForumMagnum aka LessWrong/EA Forum) despite us not intentionally using any async modules, because some libraries, if listed in serverExternalPackages in NextConfig, are treated as async modules even if there's nothing async inside them (presumably because turbopack is making conservative assumptions without looking inside the library).

I set up a local turbopack build and patched compute_async_module_info_single to do a second pass of graph.traverse_edges_from_entries_dfs, confirmed that this was marking additional modules as async, and confirmed that it fixed the issue in the places we were seeing it.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:49 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6000
  Available memory (MB): 65536
  Available CPU cores: 10
Binaries:
  Node: 22.21.1
  npm: 10.9.4
  Yarn: N/A
  pnpm: 9.6.0
Relevant Packages:
  next: 16.0.2-canary.13
  eslint-config-next: N/A
  react: 19.3.0-canary-fa50caf5-20251107
  react-dom: 19.3.0-canary-fa50caf5-20251107
  typescript: 5.9.2
Next.js Config:
  output: N/A

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

Turbopack

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

next build (local)

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    TurbopackRelated to Turbopack with Next.js.linear: turbopackConfirmed issue that is tracked by the Turbopack team.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions