[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[Flight] Don't serialize toJSON in Debug path and omit wide arrays - #34759

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo
Oct 7, 2025
Merged

[Flight] Don't serialize toJSON in Debug path and omit wide arrays#34759
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:streamydebuginfo

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a toJSON on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it.

Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.

This shows that the Buffer is serialized its toJSON form.
…nderDebugModel
This by-passes whatever toJSON generates. We want to serialize the original value.
In the case of Buffer we want to serialize that as a typed array.
This helps solve it when the root value of a Promise is too large to
include.
In the debug channel case this would be deferred earlier.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Oct 7, 2025
@react-sizebot

Copy link
Copy Markdown

Comparing: a4eb2df...6e44670

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js=663.95 kB663.96 kB+0.01%117.03 kB117.04 kB
facebook-www/ReactDOM-prod.classic.js=687.81 kB687.83 kB+0.01%121.07 kB121.08 kB
facebook-www/ReactDOM-prod.modern.js=678.24 kB678.26 kB+0.01%119.42 kB119.44 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-server/cjs/react-server-flight.development.js+0.66%146.09 kB147.05 kB+0.60%26.07 kB26.23 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-stable/react-server/cjs/react-server-flight.development.js+0.57%141.85 kB142.66 kB+0.44%25.29 kB25.40 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.51%189.86 kB190.82 kB+0.52%34.45 kB34.63 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.50%193.94 kB194.90 kB+0.44%35.03 kB35.19 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.49%197.71 kB198.67 kB+0.47%35.81 kB35.98 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.49%198.18 kB199.14 kB+0.47%35.91 kB36.08 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.43%36.37 kB36.53 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.48%201.83 kB202.79 kB+0.42%36.37 kB36.53 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.45%214.18 kB215.15 kB+0.46%39.08 kB39.26 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js+0.44%185.60 kB186.41 kB+0.39%33.65 kB33.78 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.44%220.70 kB221.66 kB+0.45%39.76 kB39.94 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js+0.43%189.33 kB190.14 kB+0.36%34.19 kB34.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.42%227.41 kB228.37 kB+0.48%40.84 kB41.04 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js+0.42%193.45 kB194.26 kB+0.37%35.03 kB35.15 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.42%228.62 kB229.58 kB+0.48%41.14 kB41.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.42%228.67 kB229.63 kB+0.48%41.14 kB41.34 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js+0.42%193.92 kB194.73 kB+0.37%35.13 kB35.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.54 kB35.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js+0.41%197.21 kB198.03 kB+0.38%35.55 kB35.69 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js+0.39%209.81 kB210.63 kB+0.26%38.29 kB38.39 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js+0.38%216.33 kB217.14 kB+0.25%38.98 kB39.08 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js+0.37%223.04 kB223.85 kB+0.32%40.05 kB40.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js+0.36%224.24 kB225.06 kB+0.31%40.35 kB40.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js+0.36%224.30 kB225.11 kB+0.30%40.36 kB40.48 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-server/cjs/react-server-flight.production.js=65.67 kB65.53 kB=12.99 kB12.93 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js=663.71 kB662.24 kB=104.89 kB104.76 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js=663.66 kB662.19 kB=104.87 kB104.73 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js=663.64 kB662.16 kB=104.87 kB104.73 kB
facebook-react-native/react/cjs/React-dev.js=52.05 kB51.90 kB=11.57 kB11.55 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js=324.45 kB323.14 kB=56.52 kB56.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js=324.27 kB322.97 kB=56.48 kB56.35 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js=324.20 kB322.89 kB=56.46 kB56.32 kB
facebook-react-native/react/cjs/React-profiling.js=19.66 kB19.58 kB=5.03 kB5.02 kB
facebook-react-native/react/cjs/React-prod.js=19.23 kB19.15 kB=4.95 kB4.94 kB
facebook-www/React-profiling.classic.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-profiling.modern.js=20.57 kB20.47 kB=5.25 kB5.24 kB
facebook-www/React-prod.classic.js=20.14 kB20.04 kB=5.17 kB5.16 kB
facebook-www/React-prod.modern.js=20.14 kB20.04 kB=5.17 kB5.16 kB
oss-stable/react/cjs/react.react-server.development.js=30.71 kB30.51 kB=7.33 kB7.29 kB
oss-stable-semver/react/cjs/react.react-server.development.js=30.69 kB30.48 kB=7.31 kB7.26 kB
oss-stable/react/cjs/react.react-server.production.js=13.54 kB13.41 kB=3.74 kB3.70 kB
oss-stable-semver/react/cjs/react.react-server.production.js=13.51 kB13.39 kB=3.71 kB3.68 kB
oss-stable-semver/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable/react-is/cjs/react-is.development.js=5.03 kB4.98 kB=1.13 kB1.12 kB
oss-stable-semver/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable/react-is/cjs/react-is.production.js=4.49 kB4.44 kB=1.07 kB1.07 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-dev-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable/react/cjs/react-jsx-runtime.react-server.development.js=13.36 kB13.21 kB=3.53 kB3.49 kB
oss-stable-semver/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable/react-server/cjs/react-server.development.js=203.99 kB201.63 kB=35.95 kB35.59 kB
oss-stable-semver/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable/react/cjs/react-jsx-runtime.development.js=12.60 kB12.45 kB=3.40 kB3.36 kB
oss-stable-semver/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable/react/cjs/react-jsx-dev-runtime.development.js=12.20 kB12.06 kB=3.39 kB3.35 kB
oss-stable-semver/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-server/cjs/react-server.production.js=142.39 kB140.44 kB=25.03 kB24.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js=433.37 kB425.55 kB=77.21 kB75.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js=433.30 kB425.47 kB=77.16 kB75.85 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js=432.59 kB424.76 kB=77.03 kB75.70 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js=432.52 kB424.69 kB=76.99 kB75.65 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js=402.59 kB395.25 kB=72.89 kB71.31 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js=402.57 kB395.23 kB=72.87 kB71.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js=402.56 kB395.23 kB=72.87 kB71.28 kB
oss-stable/react/cjs/react.development.js=48.27 kB47.24 kB=10.97 kB10.73 kB
oss-stable-semver/react/cjs/react.development.js=48.24 kB47.22 kB=10.95 kB10.71 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js=439.83 kB429.53 kB=77.01 kB74.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js=439.75 kB429.45 kB=76.96 kB74.67 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js=364.39 kB354.76 kB=70.83 kB68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js=364.32 kB354.69 kB=70.80 kB68.54 kB
oss-stable/react/cjs/react.production.js=17.77 kB17.24 kB=4.59 kB4.45 kB
oss-stable-semver/react/cjs/react.production.js=17.74 kB17.22 kB=4.57 kB4.42 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js=283.23 kB273.72 kB=51.42 kB49.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js=283.16 kB273.64 kB=51.39 kB49.96 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js=277.68 kB268.16 kB=49.32 kB47.91 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js=277.60 kB268.09 kB=49.29 kB47.88 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js=254.53 kB242.85 kB=46.87 kB44.98 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js=254.50 kB242.82 kB=46.85 kB44.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.67 kB237.99 kB=45.01 kB43.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js=249.64 kB237.96 kB=44.99 kB43.09 kB
react-native/implementations/ReactNativeRenderer-dev.js=782.76 kB745.01 kB=125.01 kB118.74 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js=291.42 kB277.15 kB=51.27 kB48.55 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js=291.35 kB277.07 kB=51.25 kB48.53 kB
oss-stable/react-art/cjs/react-art.development.js=695.00 kB657.75 kB=109.48 kB103.44 kB
oss-stable-semver/react-art/cjs/react-art.development.js=694.93 kB657.68 kB=109.46 kB103.42 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js=259.70 kB245.43 kB=47.17 kB44.73 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js=259.62 kB245.35 kB=47.14 kB44.70 kB
react-native/implementations/ReactFabric-dev.js=768.82 kB725.92 kB=122.86 kB115.66 kB
oss-stable/react-art/cjs/react-art.production.js=329.05 kB310.46 kB=55.80 kB52.71 kB
oss-stable-semver/react-art/cjs/react-art.production.js=328.98 kB310.38 kB=55.77 kB52.68 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js=798.76 kB751.67 kB=125.12 kB117.48 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js=798.73 kB751.65 kB=125.09 kB117.45 kB
react-native/implementations/ReactFabric-prod.js=384.01 kB360.97 kB=66.40 kB62.42 kB
react-native/implementations/ReactNativeRenderer-profiling.js=470.34 kB439.89 kB=78.97 kB73.88 kB
react-native/implementations/ReactNativeRenderer-prod.js=404.32 kB376.32 kB=69.53 kB64.91 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js=1,170.62 kB1,084.10 kB=195.33 kB179.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js=1,170.49 kB1,083.97 kB=195.30 kB179.80 kB
react-native/implementations/ReactFabric-profiling.js=458.90 kB424.67 kB=77.16 kB71.25 kB
oss-stable/react-dom/cjs/react-dom-client.development.js=1,154.24 kB1,067.72 kB=192.52 kB176.99 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js=1,154.11 kB1,067.59 kB=192.49 kB176.96 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js=516.82 kB475.86 kB=80.85 kB74.50 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js=516.80 kB475.83 kB=80.83 kB74.48 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js=446.17 kB407.80 kB=71.38 kB65.68 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js=446.15 kB407.78 kB=71.36 kB65.66 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js=679.11 kB607.76 kB=117.84 kB104.76 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js=678.98 kB607.63 kB=117.81 kB104.73 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=604.98 kB536.14 kB=107.14 kB94.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js=604.86 kB536.02 kB=107.11 kB94.78 kB

Generated by 🚫 dangerJS against 6e44670

@sebmarkbage
sebmarkbage merged commit 3025aa3 into react:mainOct 7, 2025
246 of 247 checks passed
github-actionsBot pushed a commit that referenced this pull request Oct 7, 2025
…34759)
There's a couple of issues with serializing Buffer in the debug renders.
For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.
Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
DiffTrain build for [3025aa3](3025aa3)
unstubbable pushed a commit to vercel/next.js that referenced this pull request Oct 8, 2025
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws (like our
own ClientReference proxies) unwound the entire stringify before the
replacer's per-property error handling could run. The debug path
already reads the original values to bypass toJSON (#34759); the probe
was the remaining way for a user value to take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol, like our own ClientReference proxies. Values that
reach a component through a userland thenable are recorded as such,
since the io entry that unblocks them describes whatever context
settled the thenable rather than the leaf that produced the value, and
React cannot see through the thenable to forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, and the client threw
away the rest of that component's debug info trying to initialize
them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
On failure, retry the same model with one added defense: each object
the replacer returns gets its properties probed the same way
JSON.stringify is about to, and the values that can't be probed are
replaced with the placeholder string, per value instead of per model.
Since the model and traversal are identical, the retry only has to
undo the failed pass: references it registered are rolled back (they
describe paths into JSON that was never written and would otherwise
turn the retry into self-references), the object limit it consumed is
restored, and the root marker it consumed is re-armed. The successful
first pass stays free of the extra reads.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 26, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gaearon added a commit that referenced this pull request Jul 27, 2026
When JSON.stringify of an outlined debug model threw, the whole model
was replaced with a placeholder string. The row's type changed out
from under everything that referenced it: rows that named the
component as an owner now pointed at a string, references into the
row's properties pointed into JSON that was never written since the
references are registered while the failed pass runs, and the client
threw away the rest of that component's debug info trying to
initialize them.
JSON.stringify reads every property of the objects the replacer
returns and probes each object property's toJSON before the replacer
can substitute it, so a value whose property access throws unwound the
entire stringify before the replacer's per-property error handling
could run. Our own ClientReference proxies exempt the toJSON probe
specifically, but userland reference proxies of the same shape throw
on it. The debug path already reads the original values to bypass
toJSON (#34759); the probe was the remaining way for a user value to
take down the whole model.
Run the same probes over each object the replacer returns before
JSON.stringify does. The common case is a scan that allocates nothing
and returns the model as is; only a model that would have thrown gets
copied, with the values that can't be probed replaced by the
placeholder string, per value instead of per model. Since the pass
never fails, no references are ever registered against JSON that
doesn't get written.
The fuzzer now exercises this: promise props are sometimes delivered
behind a proxy that throws for any property access outside the
thenable protocol. Values that reach a component through a userland
thenable are recorded as such, since the io entry that unblocks them
describes whatever context settled the thenable rather than the leaf
that produced the value, and React cannot see through the thenable to
forward the real chain.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@unstubbable