Skip to content

Conversation

@jere-co
Copy link

@jere-co jere-co commented Nov 28, 2025

The visitAsyncNode function used null as both an "in progress" marker and a valid cached result. When a node was revisited during its own traversal (circular reference), the function returned null as if it were a cached result, which could lead to infinite recursion.

This bug was introduced in #35005 when visited was changed from a Set to a Map for caching return values.

This change:

  • Introduces an IN_PROGRESS sentinel Symbol to distinguish nodes currently being evaluated from cached results
  • Returns null on cycle detection to indicate "no I/O found on this cyclic path" (not undefined, which would signal abort semantics and skip emitting I/O info for other non-cyclic branches)
  • Always caches the computed result after visitAsyncNodeImpl returns, including null/undefined values, so revisits get the real computed value instead of the sentinel

This fixes RangeError: Maximum call stack size exceeded that occurs in Next.js 15.5.0+ when using database clients like Gel/EdgeDB that create circular promise chains in their async sequences.

How did you test this change?

Validated manually in Next.js apps (15.5, 16.0.5, 16.1.0-canary) using the Gel/EdgeDB repro that previously hit the stack overflow. No React unit test added; the internal module system made mocking getAsyncSequenceFromPromise impractical from the test suite. Fix verified via integration runs only.

Reproduction Repository

https://github.com/jere-co/next-debug

This repository contains:

  • A minimal reproduction case triggering the bug
  • Step-by-step instructions to reproduce and verify the fix
  • Root cause analysis in /docs
  • A temporary patch script for affected projects

The visitAsyncNode function used null as both an "in progress" marker
and a valid cached result. When a node was revisited during its own
traversal (circular reference), the function returned null as if it were
a cached result, which could lead to infinite recursion.

This change:
- Introduces an IN_PROGRESS sentinel Symbol to distinguish nodes
  currently being evaluated from cached results
- Returns null on cycle detection to indicate "no I/O found on this
  cyclic path" (not undefined, which would signal abort semantics and
  skip emitting I/O info for other non-cyclic branches)
- Always caches the computed result after visitAsyncNodeImpl returns,
  including null/undefined values

This fixes RangeError: Maximum call stack size exceeded that occurs
in Next.js 15.5.0+ when using database clients like Gel/EdgeDB that
create circular promise chains in their async sequences.
@meta-cla
Copy link

meta-cla bot commented Nov 28, 2025

Hi @jere-co!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at [email protected]. Thanks!

@meta-cla
Copy link

meta-cla bot commented Nov 28, 2025

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla bot added the CLA Signed label Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant