Repository: facebook/react
Pull request: [Flight] Only use debug component info for parent stacks

Follow-up to https://github.com/facebook/react/pull/34229

We use Component debug info to fill in gaps between Fibers in the parent stack (for React DevTools, errorInfo.componentStack, hydration diffs, etc). It's unfortunate that this makes third-party Server Components disappear.

Before:
<img width="506" height="254" alt="CleanShot 2025-09-09 at 17 17 54@2x" src="https://github.com/user-attachments/assets/d5f5ac18-2836-4794-8881-8cb938ba754d" />
After:
<img width="444" height="218" alt="CleanShot 2025-09-09 at 17 18 19@2x" src="https://github.com/user-attachments/assets/ef2e5510-2c72-43d6-a6a0-05524bf0e987" />




Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -1794,13 +1794,21 @@ function transferReferencedDebugInfo(
         existingDebugInfo.push.apply(existingDebugInfo, referencedDebugInfo);
       }
     }
-    // We also add it to the initializing chunk since the resolution of that promise is
-    // also blocked by these. By adding it to both we can track it even if the array/element
+    // We also add the debug info to the initializing chunk since the resolution of that promise is
+    // also blocked by the referenced debug info. By adding it to both we can track it even if the array/element
     // is extracted, or if the root is rendered as is.
     if (parentChunk !== null) {
       const parentDebugInfo = parentChunk._debugInfo;
-      // $FlowFixMe[method-unbinding]
-      parentDebugInfo.push.apply(parentDebugInfo, referencedDebugInfo);
+      for (let i = 0; i < referencedDebugInfo.length; ++i) {
+        const debugInfoEntry = referencedDebugInfo[i];
+        if (debugInfoEntry.name != null) {
+          (debugInfoEntry: ReactComponentInfo);
+          // We're not transferring Component info since we use Component info
+          // in Debug info to fill in gaps between Fibers for the parent stack.
+        } else {
+          parentDebugInfo.push(debugInfoEntry);
+        }
+      }
     }
   }
 }

filename: packages/react-client/src/__tests__/ReactFlight-test.js
patch: @@ -2960,13 +2960,6 @@ describe('ReactFlight', () => {
               {
                 time: 16,
               },
-              {
-                env: 'third-party',
-                key: null,
-                name: 'ThirdPartyAsyncIterableComponent',
-                props: {},
-                stack: '    in Object.<anonymous> (at **)',
-              },
               {
                 time: 16,
               },
@@ -2995,13 +2988,6 @@ describe('ReactFlight', () => {
               {
                 time: 19,
               },
-              {
-                env: 'third-party',
-                key: null,
-                name: 'ThirdPartyAsyncIterableComponent',
-                props: {},
-                stack: '    in Object.<anonymous> (at **)',
-              },
               {time: 19},
             ]
           : undefined,
@@ -3847,4 +3833,115 @@ describe('ReactFlight', () => {
 
     expect(ReactNoop).toMatchRenderedOutput(<div>not using props</div>);
   });
+
+  // @gate !__DEV__ || enableComponentPerformanceTrack
+  it('produces correct parent stacks', async () => {
+    function Container() {
+      return ReactServer.createElement('div', null);
+    }
+    function ContainerParent() {
+      return ReactServer.createElement(Container, null);
+    }
+    function App() {
+      return ReactServer.createElement(
+        'main',
+        null,
+        ReactServer.createElement(ContainerParent, null),
+      );
+    }
+
+    const transport = ReactNoopFlightServer.render({
+      root: ReactServer.createElement(App, null),
+    });
+
+    await act(async () => {
+      const {root} = await ReactNoopFlightClient.read(transport);
+
+      ReactNoop.render(root);
+
+      expect(root.type).toBe('main');
+      if (__DEV__) {
+        const div = root.props.children;
+        expect(getDebugInfo(div)).toEqual([
+          {
+            time: 14,
+          },
+          {
+            env: 'Server',
+            key: null,
+            name: 'ContainerParent',
+            owner: {
+              env: 'Server',
+              key: null,
+              name: 'App',
+              props: {},
+              stack: '    in Object.<anonymous> (at **)',
+            },
+            props: {},
+            stack: '    in App (at **)',
+          },
+          {
+            time: 15,
+          },
+          {
+            env: 'Server',
+            key: null,
+            name: 'Container',
+            owner: {
+              env: 'Server',
+              key: null,
+              name: 'ContainerParent',
+              owner: {
+                env: 'Server',
+                key: null,
+                name: 'App',
+                props: {},
+                stack: '    in Object.<anonymous> (at **)',
+              },
+              props: {},
+              stack: '    in App (at **)',
+            },
+            props: {},
+            stack: '    in ContainerParent (at **)',
+          },
+          {
+            time: 16,
+          },
+        ]);
+        expect(getDebugInfo(root)).toEqual([
+          {
+            time: 12,
+          },
+          {
+            env: 'Server',
+            key: null,
+            name: 'App',
+            props: {},
+            stack: '    in Object.<anonymous> (at **)',
+          },
+          {
+            time: 13,
+          },
+          {
+            time: 14,
+          },
+          {
+            time: 15,
+          },
+          {
+            time: 16,
+          },
+        ]);
+      } else {
+        expect(root._debugInfo).toBe(undefined);
+        expect(root._owner).toBe(undefined);
+      }
+    });
+
+    expect(ReactNoop).toMatchRenderedOutput(
+      <main>
+        <div />
+      </main>,
+    );
+  });
 });