[GH-ISSUE #6344] [Bug] analyze-web-bundle fatally exhausts the CLI heap (4 GiB and 12 GiB) on a 173 KB react-dom chunk instead of returning a typed resource_constraint #3825

Open
opened 2026-10-31 00:44:24 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @markdhughes on GitHub (Oct 22, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6344

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

rea analyze-web-bundle against a public Next.js page (https://svgconverter.online/) exhausts the main CLI process heap and dies with Node's FATAL ERROR: Ineffective mark-compacts near heap limit, exit code 134, and no Evidence record. It failed at the default heap (4 GiB) and again with NODE_OPTIONS=--max-old-space-size=12288 (12 GiB, ~344 s before the crash). The crash happens in the main process, not in an owned worker, so there is no resource_constraint result and no partial Evidence.

The same page analyzed through the export route (inspect-web-page --include-script-sources → export-web-scripts → analyze-javascript-application) completes, but marks one file as interrupted:

files/isolated/source-6-….js: Resource constraint during analyze_javascript_application: Owned JavaScript worker exhausted its heap while analyzing files/isolated/source-6-….js during static …

That file is the page's react-dom chunk: https://svgconverter.online/_next/static/chunks/2200cc46-a6ae7a7e0a97a1bf.js?dpl=dpl_AJcq5Kt68x1nchVAikjSPUtNBUwy, 172,834 bytes, SHA-256 of the exported file a30e6ccdda55dc70b32c9f3e24b604facc386bd698de03127b782501552ec238. The other 11 first-party scripts (up to 124 KB) finished. So the outlier is a 173 KB minified React DOM webpack factory, which is probably the same traversal blow-up as #6127, but surfacing as a fatal crash of the whole command instead of a bounded worker failure.

Reproduction

  1. Launch Chrome headless with a throwaway profile: chrome --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --user-data-dir=<tmp> --headless=new https://svgconverter.online/
  2. rea list-browser-targets http://127.0.0.1:9222 → one page target.
  3. rea analyze-web-bundle http://127.0.0.1:9222 <targetId> --observation-ms 2000 --format json
  4. Observe the process grow to the heap limit and abort with exit 134.

Reproduced 2/2: once at the default heap with --fetch-source-maps (while an inspect-web-page capture ran on the same target), once alone with a 12 GiB heap and no source maps. The page is public, so the chunk URL above is a shareable input.

Expected behavior

Either the command completes with a partial result that names the interrupted script (as analyze-javascript-application does), or it returns a typed resource_constraint error with the offending script identity and a next step. A fatal V8 OOM with no Evidence and no pointer to the offending file is not recoverable from the CLI. Also, a 173 KB input needing more than 12 GiB suggests the cost is not proportional to input size (same expectation as #6127).

Impact and scope

analyze-web-bundle is unusable on any site that ships react-dom in a chunk, which is most React/Next.js sites. Workaround that works: inspect-web-page --include-script-sources, export-web-scripts, then analyze-javascript-application, accepting the interrupted react-dom chunk. No evidence-integrity concern observed on the workaround path; its result reports the interruption honestly. Untested: whether analyze-web-bundle honors any heap bound of its own, and whether other React builds (unminified, React 18 vs 19) behave the same.

REA version

6.7.0 (rea --version), installed with npm install --global rea-agents

Runtime and platform

Node v24.20.0; Windows 11 x64 (10.0.26300); Chrome 155.0.8059.85 headless as the CDP browser. No native provider configured.

Target and artifact context

Public Next.js app-router site on Vercel; webpack chunks; react-dom chunk 172,834 bytes; no source maps published.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Tail of stderr from the 12 GiB run (obtained by redirecting 2> to a file):

[35468:…]   343256 ms: Scavenge (interleaved) 12274.4 (12286.6) -> 12267.1 (12286.8) MB, pooled: 0 MB, 6.43 / 0.00 ms  (average mu = 0.311, current mu = 0.297) allocation failure;
[35468:…]   344651 ms: Mark-Compact (reduce) 12268.3 (12286.8) -> 12258.6 (12271.8) MB, pooled: 0 MB, 1232.03 / 0.00 ms  (+ 97.7 ms in 20 steps since start of marking, biggest step 5.0 ms, walltime since start of marking 1347 ms)
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
 1: node::OnFatalError+1343
 2: v8::Function::NewInstance+423
 …

Default-heap run (with --fetch-source-maps), tail:

119057 ms: Mark-Compact 4093.3 (4100.2) -> 4086.3 (4093.5) MB … allocation failure; GC in old space requested
119666 ms: Mark-Compact 4093.3 (4100.5) -> 4089.8 (4096.7) MB … allocation failure; GC in old space requested
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

analyze-javascript-application on the exported scripts: statistics.parsed_javascript_files: 11, skipped_javascript_files: 1, graph coverage.status: "partial" with javascript_analysis_worker_heap_limit_bytes: 1098907648; the unknown node for the react-dom file has analysis_status: "interrupted". The semantic graph for the remaining files is 62,244 nodes / 74,015 relations / 23,649 unknowns.

Missing evidence: no heap snapshot was taken; I can produce one from a debug build if useful.

Typed outcome or recovery expected

A resource_constraint result naming the script (URL + SHA-256) that exceeded the budget, with the rest of the bundle analysis retained, or at minimum a documented --max-heap-mb for analyze-web-bundle so the failure is bounded and typed instead of fatal.

Analysis or suspected cause

Hypothesis only: analyze-web-bundle runs the static JavaScript analysis in the CLI's own process rather than in the bounded owned worker that analyze-javascript-application uses, so the per-file heap limit and the typed interruption path do not apply, and the same member-mutation traversal growth that #6127 / #6146 address becomes a hard crash. Not verified against source.

Additional context

Related: #6127 (worker heap exhaustion on a 233 KB Turbopack chunk), #6146 (bounds the traversal), #2498, #4601. If #6146 fixes the traversal cost, this issue reduces to "analyze-web-bundle should fail typed rather than fatal".

Originally created by @markdhughes on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6344 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area CLI ### Observed behavior `rea analyze-web-bundle` against a public Next.js page (https://svgconverter.online/) exhausts the main CLI process heap and dies with Node's `FATAL ERROR: Ineffective mark-compacts near heap limit`, exit code 134, and no Evidence record. It failed at the default heap (4 GiB) and again with `NODE_OPTIONS=--max-old-space-size=12288` (12 GiB, ~344 s before the crash). The crash happens in the main process, not in an owned worker, so there is no `resource_constraint` result and no partial Evidence. The same page analyzed through the export route (`inspect-web-page --include-script-sources` → `export-web-scripts` → `analyze-javascript-application`) completes, but marks one file as interrupted: ``` files/isolated/source-6-….js: Resource constraint during analyze_javascript_application: Owned JavaScript worker exhausted its heap while analyzing files/isolated/source-6-….js during static … ``` That file is the page's react-dom chunk: `https://svgconverter.online/_next/static/chunks/2200cc46-a6ae7a7e0a97a1bf.js?dpl=dpl_AJcq5Kt68x1nchVAikjSPUtNBUwy`, 172,834 bytes, SHA-256 of the exported file `a30e6ccdda55dc70b32c9f3e24b604facc386bd698de03127b782501552ec238`. The other 11 first-party scripts (up to 124 KB) finished. So the outlier is a 173 KB minified React DOM webpack factory, which is probably the same traversal blow-up as #6127, but surfacing as a fatal crash of the whole command instead of a bounded worker failure. ### Reproduction 1. Launch Chrome headless with a throwaway profile: `chrome --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --user-data-dir=<tmp> --headless=new https://svgconverter.online/` 2. `rea list-browser-targets http://127.0.0.1:9222` → one page target. 3. `rea analyze-web-bundle http://127.0.0.1:9222 <targetId> --observation-ms 2000 --format json` 4. Observe the process grow to the heap limit and abort with exit 134. Reproduced 2/2: once at the default heap with `--fetch-source-maps` (while an `inspect-web-page` capture ran on the same target), once alone with a 12 GiB heap and no source maps. The page is public, so the chunk URL above is a shareable input. ### Expected behavior Either the command completes with a partial result that names the interrupted script (as `analyze-javascript-application` does), or it returns a typed `resource_constraint` error with the offending script identity and a next step. A fatal V8 OOM with no Evidence and no pointer to the offending file is not recoverable from the CLI. Also, a 173 KB input needing more than 12 GiB suggests the cost is not proportional to input size (same expectation as #6127). ### Impact and scope `analyze-web-bundle` is unusable on any site that ships react-dom in a chunk, which is most React/Next.js sites. Workaround that works: `inspect-web-page --include-script-sources`, `export-web-scripts`, then `analyze-javascript-application`, accepting the interrupted react-dom chunk. No evidence-integrity concern observed on the workaround path; its result reports the interruption honestly. Untested: whether `analyze-web-bundle` honors any heap bound of its own, and whether other React builds (unminified, React 18 vs 19) behave the same. ### REA version 6.7.0 (`rea --version`), installed with `npm install --global rea-agents` ### Runtime and platform Node v24.20.0; Windows 11 x64 (10.0.26300); Chrome 155.0.8059.85 headless as the CDP browser. No native provider configured. ### Target and artifact context Public Next.js app-router site on Vercel; webpack chunks; react-dom chunk 172,834 bytes; no source maps published. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Tail of stderr from the 12 GiB run (obtained by redirecting `2>` to a file): ``` [35468:…] 343256 ms: Scavenge (interleaved) 12274.4 (12286.6) -> 12267.1 (12286.8) MB, pooled: 0 MB, 6.43 / 0.00 ms (average mu = 0.311, current mu = 0.297) allocation failure; [35468:…] 344651 ms: Mark-Compact (reduce) 12268.3 (12286.8) -> 12258.6 (12271.8) MB, pooled: 0 MB, 1232.03 / 0.00 ms (+ 97.7 ms in 20 steps since start of marking, biggest step 5.0 ms, walltime since start of marking 1347 ms) FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory ----- Native stack trace ----- 1: node::OnFatalError+1343 2: v8::Function::NewInstance+423 … ``` Default-heap run (with `--fetch-source-maps`), tail: ``` 119057 ms: Mark-Compact 4093.3 (4100.2) -> 4086.3 (4093.5) MB … allocation failure; GC in old space requested 119666 ms: Mark-Compact 4093.3 (4100.5) -> 4089.8 (4096.7) MB … allocation failure; GC in old space requested FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory ``` `analyze-javascript-application` on the exported scripts: `statistics.parsed_javascript_files: 11`, `skipped_javascript_files: 1`, graph `coverage.status: "partial"` with `javascript_analysis_worker_heap_limit_bytes: 1098907648`; the `unknown` node for the react-dom file has `analysis_status: "interrupted"`. The semantic graph for the remaining files is 62,244 nodes / 74,015 relations / 23,649 unknowns. Missing evidence: no heap snapshot was taken; I can produce one from a debug build if useful. ### Typed outcome or recovery expected A `resource_constraint` result naming the script (URL + SHA-256) that exceeded the budget, with the rest of the bundle analysis retained, or at minimum a documented `--max-heap-mb` for `analyze-web-bundle` so the failure is bounded and typed instead of fatal. ### Analysis or suspected cause Hypothesis only: `analyze-web-bundle` runs the static JavaScript analysis in the CLI's own process rather than in the bounded owned worker that `analyze-javascript-application` uses, so the per-file heap limit and the typed interruption path do not apply, and the same member-mutation traversal growth that #6127 / #6146 address becomes a hard crash. Not verified against source. ### Additional context Related: #6127 (worker heap exhaustion on a 233 KB Turbopack chunk), #6146 (bounds the traversal), #2498, #4601. If #6146 fixes the traversal cost, this issue reduces to "analyze-web-bundle should fail typed rather than fatal".
Author
Owner

@muslimbarahooe007-max commented on GitHub (Oct 23, 2026):

I traced this one to the source on 6.7.0 (macOS arm64, Node 26.12.1). The blow-up is not in member-mutation traversal, so #6146 is unlikely to fix it on its own: it happens earlier, in returned-reference expansion during ambient-write indexing.

Root cause

analyzeScript (webBundleAnalyzerInspection) calls index.indexAmbientWritesSteps() → indexJavaScriptAmbientWritesSteps → createAmbientPathResolver → continueReturned → createSemanticReturnedReferences (src/domain/javascript/javascriptSemanticReturnedReferences.ts).

That worklist carries the set of initializers already used on the current chain, and reuses an earlier expansion of a (node, path) state only when a stored context is a subset of the current one (contextContains). In minified React DOM, five bindings (e, l, a, r, n inside completeWork) are reassigned from each other's members many times (a=ai(e), e=a.updateQueue, e=n, n=n.sibling, r=t.child, ...). Every distinct ordering of those initializers produces a distinct context, so the memo almost never hits and the walk enumerates simple paths through the alias graph: exponential, mostly short-lived garbage. A CPU profile puts 76% of time in that worklist; the heap profile at exit holds only ~27 MB, so it is allocation churn rather than retention.

Because analyze_web_bundle runs this in the server process (not the capped worker analyze_javascript_application uses), the V8 abort takes the whole MCP server down, which the client sees only as "Connection closed".

Reproduction from public files only

npm pack react-dom@19.2.8 && tar xzf react-dom-19.2.8.tgz
mkdir repro && cp package/cjs/react-dom-client.production.js repro/
printf '<!doctype html><script src="/react-dom-client.production.js"></script>\n' > repro/index.html
(cd repro && python3 -m http.server 8765 --bind 127.0.0.1) &
chrome --headless=new --remote-debugging-port=9333 --user-data-dir=$(mktemp -d) http://127.0.0.1:8765/
# then call analyze_web_bundle over MCP (or rea analyze-web-bundle) with allowed_origins ["http://127.0.0.1:8765"]

Measured through the MCP server (rea mcp, tools/call analyze_web_bundle):

Outcome Time Peak memory
6.7.0 server killed, SIGABRT, Ineffective mark-compacts near heap limit 70.7 s 4.75 GB
6.7.0 + patch below result returned, isError: false 5.2 s 0.71 GB

The same file through analyzeScript alone: 6.7.0 aborts even at --max-old-space-size=4096; patched finishes in 3.7 s at 361 MB.

Cut-down trigger (1.7 KB, from minified React DOM completeWork): 6.7.0 needs ~340 MB for it; the full 6 KB function needs ~1 GB
var __x=function(e,t,n){var r=t.pendingProps;switch(r$(t),t.tag){case 3:return r=null,null!==e&&(r=e.memoizedState.cache),eo(),iy(t),null;case 26:var l=t.type,a=t.memoizedState;return null===e?(ip(t)):a?a!==e.memoizedState?(ip(t),iy(t)):(iy(t)):((e=e.memoizedProps)!==r&&ip(t),iy(t)),null;case 5:if(eu(t),l=t.type)e.memoizedProps!==r&&ip(t);else{if(r1(t))rJ(t);else{switch(a){default:switch(l){case"script":a=a.removeChild(a.firstChild);break;}}}}case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&ip(t);else{if(r1(t)){if(r=null)switch(l.tag){case 5:r=l.memoizedProps}}}case 31:if(n=t.memoizedState){if(r=r1(t)){if(null===e){if(!(e=null!==(e=t.memoizedState)?e.dehydrated:null))throw Error(u(557));}else r2();iy(t),e=!1}else e=!0;}case 13:if(r=t.memoizedState){if(l=r1(t)){if(null===e){if(!(l=null!==(l=t.memoizedState)?l.dehydrated:null))throw Error(u(317));}else r2();iy(t),l=!1}else l=r3(),l=!0;}return n&&(r=t.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool),a=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool)),iy(t),null;case 19:if(null===(a=r.rendering))if(l)iv(r,!1);else{if(0!==uD||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(a=ai(e))){for(t.updateQueue=e=a.updateQueue,e=n;null!==n;)n=n.sibling;}e=e.sibling}}else{if(!l)if(null!==(e=ai(a))){if(l=!0,t.updateQueue=e=e.updateQueue)return iy(t),null}else 2*ey()-r.renderingStartTime>uX&&0x20000000!==n&&(l=!0);r.isBackwards?(t.child=a):(null!==(e=r.last)?e.sibling=a:t.child=a)}if(null!==r.tail){e=r.tail;e:{for(n=e;null!==n;){n=n.sibling}n=!0}}case 24:return null!==e&&(n=e.memoizedState.cache),iy(t),null;}};

Suggested fix (tested)

Give the returned-reference worklist a budget, following the getter-expansion precedent:

  • Count visited states per callee. Up to SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT (4096) behaviour is unchanged.
  • Past the budget, record a new returned-reference-expansion resource limit on state.valueResourceLimits and expand each (node, pathKey) at most once regardless of context. The per-chain initializer blocking stays, so n=n.sibling style self-reassignment still cannot grow paths without end.
  • Hard stop at 16 × the budget as a final termination guarantee.
  • Surface it in the bundle result: an unknown with dimension: "javascript_semantics" naming the affected scripts, and completeness.status: "partial". Today analyze_web_bundle does not carry semantic resource limits into its output at all (that is also true of getter-expansion), so a bounded result would otherwise look complete.

Validation, all in the same patched 6.7.0 build:

  • 17 scripts captured from a production Next.js 16.3.8 / React 19.2.8 page, plus the 6 KB function and the 1.7 KB cut-down, each run in a separate process capped at 512 MB. 6.7.0: 2 out of memory, 17 pass. Patched: 19 pass.
  • Findings (edges, routes, endpoints, WebMCP, inferences) are byte-identical to 6.7.0 for every input 6.7.0 completes. They are also identical for the 6 KB function, comparing against 6.7.0 given an 8 GB heap, even though it crosses the budget.
  • The budget is reached only by the React DOM chunk (21 callees) and the two cut-downs (1 callee each). None of the 16 ordinary scripts get near it.
  • End to end on the real page: same two endpoint candidates as analyze_javascript_application on the exported scripts, with the React chunk named in the new unknown.

Caveats:

  • 4096 is a judgement, not tuned.
  • After the budget the walk is context-insensitive, so in principle it can miss returned functions that only a different alias context would reach. That is why it is reported as partial rather than silently.
  • I patched and tested the dist/mcp bundle only. dist/cli-runtime carries its own copy, so rea analyze-web-bundle on the CLI still aborts until the source change is rebuilt.
  • Running bundle analysis in the capped worker (the "fail typed, not fatal" part of this issue) is still worth doing on its own; this patch removes the cost blow-up, not the missing isolation.
Patch against the published 6.7.0 dist/mcp files (applies with patch -p1; source modules: javascriptSemanticReturnedReferences, javascriptSemanticResourceLimits, javascriptSemanticCoverage, JavaScriptAnalysisWorkerSchemas, webBundleAnalyzerInspection, webBundleAnalyzerResult)
diff -ru a/dist/mcp/browserObservation-DUraZs62.js b/dist/mcp/browserObservation-DUraZs62.js
--- a/dist/mcp/browserObservation-DUraZs62.js	2026-10-24 02:14:20
+++ b/dist/mcp/browserObservation-DUraZs62.js	2026-10-24 02:14:20
@@ -133,6 +133,7 @@
 				node: endpoint.node,
 				accumulator
 			});
+			if (index.bindings.valueResourceLimits.has("returned-reference-expansion")) accumulator.boundedScripts.push(script.script_key);
 		};
 		inspectNode = (script, node, accumulator, openReceiverFacts, isMapLookup) => {
 			if ((t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) && node.source !== undefined) addEdge({
@@ -346,7 +347,8 @@
 			visitedNodes: 0,
 			parsedScripts: 0,
 			failedScripts: [],
-			recoveredScripts: []
+			recoveredScripts: [],
+			boundedScripts: []
 		});
 		isIncludedScript = (script) => script.source.included;
 		routeCallNames = [
@@ -421,7 +423,7 @@
 			const omittedScripts = captureBudgetOmission(inspection, "scripts");
 			const omittedSourceMaps = sourceMaps.status === "not_requested" ? undefined : captureBudgetOmission(inspection, "source_maps");
 			const sourceMapIncomplete = sourceMaps.status === "partial" || sourceMaps.status === "unavailable";
-			const partial = accumulator.failedScripts.length > 0 || accumulator.recoveredScripts.length > 0 || unavailable.length > 0 || sourceMapIncomplete || omittedScripts !== undefined || omittedSourceMaps !== undefined;
+			const partial = accumulator.failedScripts.length > 0 || accumulator.recoveredScripts.length > 0 || accumulator.boundedScripts.length > 0 || unavailable.length > 0 || sourceMapIncomplete || omittedScripts !== undefined || omittedSourceMaps !== undefined;
 			return webBundleAnalysisSchema.parse({
 				capture: buildCaptureObservation(inspection, sourceScripts),
 				observations: {
@@ -439,6 +441,7 @@
 					unavailable,
 					failedScripts: accumulator.failedScripts,
 					recoveredScripts: accumulator.recoveredScripts,
+					boundedScripts: accumulator.boundedScripts,
 					sourceMaps,
 					omittedScripts,
 					omittedSourceMaps
@@ -495,6 +498,11 @@
 				dimension: "javascript_ast",
 				reason: "The parser recovered from errors in one or more source artifacts; findings may be incomplete",
 				affected_script_keys: [...input.recoveredScripts]
+			}],
+			...input.boundedScripts.length === 0 ? [] : [{
+				dimension: "javascript_semantics",
+				reason: "Returned-reference expansion reached its budget (maximum 4096 context-sensitive states per callee) in one or more source artifacts; alias-derived findings may be incomplete",
+				affected_script_keys: [...input.boundedScripts]
 			}],
 			...input.omittedSourceMaps === undefined ? [] : [{
 				dimension: "source_maps",
diff -ru a/dist/mcp/main-Cx-Z7m_n.js b/dist/mcp/main-Cx-Z7m_n.js
--- a/dist/mcp/main-Cx-Z7m_n.js	2026-10-24 02:14:20
+++ b/dist/mcp/main-Cx-Z7m_n.js	2026-10-24 02:14:20
@@ -49500,7 +49500,8 @@
 			"primitive-candidates",
 			"primitive-bytes",
 			"expression-depth",
-			"getter-expansion"
+			"getter-expansion",
+			"returned-reference-expansion"
 		]);
 		primitive = z.union([
 			z.string(),
diff -ru a/dist/mcp/webSourceLocation-DKIum0vJ.js b/dist/mcp/webSourceLocation-DKIum0vJ.js
--- a/dist/mcp/webSourceLocation-DKIum0vJ.js	2026-10-24 02:14:20
+++ b/dist/mcp/webSourceLocation-DKIum0vJ.js	2026-10-24 02:14:20
@@ -1678,18 +1678,20 @@
 
 //#endregion
 //#region dist/domain/javascript/javascriptSemanticResourceLimits.js
-var SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT, SEMANTIC_EXPRESSION_DEPTH_LIMIT, SEMANTIC_GETTER_EXPANSION_LIMIT, SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT, RESOURCE_LIMIT_REASON, jsonStringBytesFromLength, exceedsSemanticPrimitiveStringByteBudget, exceedsSemanticPrimitiveAdditionByteBudget, exceedsSemanticTemplateByteBudget, semanticResourceLimitUnknown, semanticResourceLimitReason;
+var SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT, SEMANTIC_EXPRESSION_DEPTH_LIMIT, SEMANTIC_GETTER_EXPANSION_LIMIT, SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT, SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT, RESOURCE_LIMIT_REASON, jsonStringBytesFromLength, exceedsSemanticPrimitiveStringByteBudget, exceedsSemanticPrimitiveAdditionByteBudget, exceedsSemanticTemplateByteBudget, semanticResourceLimitUnknown, semanticResourceLimitReason;
 function init_javascriptSemanticResourceLimits() {
 	return (init_javascriptSemanticResourceLimits = __esmMin((() => {
 		SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT = 256;
 		SEMANTIC_EXPRESSION_DEPTH_LIMIT = 256;
 		SEMANTIC_GETTER_EXPANSION_LIMIT = 512;
+		SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT = 4096;
 		SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT = 1048576;
 		RESOURCE_LIMIT_REASON = {
 			"primitive-candidates": "Primitive candidate budget exceeded (maximum 256 alternatives).",
 			"primitive-bytes": "Primitive string-byte budget exceeded (maximum 1048576 estimated JSON bytes).",
 			"expression-depth": "Semantic expression depth budget exceeded (maximum 256 nested expressions).",
-			"getter-expansion": "Getter expansion was conservatively bounded (maximum 512 scheduled states or a growing receiver cycle)."
+			"getter-expansion": "Getter expansion was conservatively bounded (maximum 512 scheduled states or a growing receiver cycle).",
+			"returned-reference-expansion": "Returned-reference expansion was bounded (maximum 4096 context-sensitive states per callee); later states were explored once each regardless of alias context."
 		};
 		jsonStringBytesFromLength = (codeUnits) => codeUnits * 6 + 2;
 		exceedsSemanticPrimitiveStringByteBudget = (strings) => {
@@ -4402,6 +4404,7 @@
 function init_javascriptSemanticReturnedReferences() {
 	return (init_javascriptSemanticReturnedReferences = __esmMin((() => {
 		init_javascriptAstValues();
+		init_javascriptSemanticResourceLimits();
 		init_javascriptSemanticCallableResults();
 		init_javascriptSemanticMutationInitializers();
 		init_javascriptSemanticPropertyPaths();
@@ -4441,6 +4444,9 @@
 					parent: null
 				}];
 				const expanded = new WeakMap();
+				const visitedPaths = new WeakMap();
+				let visits = 0;
+				let bounded = false;
 				while (work.length > 0) {
 					const task = work.pop();
 					if (task === undefined) break;
@@ -4451,6 +4457,19 @@
 					const { item, parent } = task;
 					const node = unwrapJavaScriptExpression(item.node).node;
 					const pathKey = JSON.stringify(item.path);
+					// Context-sensitive expansion enumerates alias chains, which grows exponentially when several
+					// bindings are reassigned from each other (minified React DOM). Past the budget, each node and
+					// path is expanded once, and the bound is reported as a resource limit.
+					visits += 1;
+					if (!bounded && visits > SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT) {
+						bounded = true;
+						state.valueResourceLimits.add("returned-reference-expansion");
+					}
+					if (visits > SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT * 16) break;
+					const paths = visitedPaths.get(node) ?? new Set();
+					if (bounded && paths.has(pathKey)) continue;
+					paths.add(pathKey);
+					visitedPaths.set(node, paths);
 					const covered = expanded.get(node)?.get(pathKey)?.find((context) => contextContains(item.initializers, context));
 					if (covered !== undefined) {
 						propagateBlockers(covered, parent);
@@ -8867,6 +8886,10 @@
 			},
 			"getter-expansion": {
 				value: 512,
+				unit: "items"
+			},
+			"returned-reference-expansion": {
+				value: 4096,
 				unit: "items"
 			}
 		};
<!-- gh-comment-id:6375818811 --> @muslimbarahooe007-max commented on GitHub (Oct 23, 2026): I traced this one to the source on 6.7.0 (macOS arm64, Node 26.12.1). The blow-up is not in member-mutation traversal, so #6146 is unlikely to fix it on its own: it happens earlier, in returned-reference expansion during ambient-write indexing. ### Root cause `analyzeScript` (`webBundleAnalyzerInspection`) calls `index.indexAmbientWritesSteps()` → `indexJavaScriptAmbientWritesSteps` → `createAmbientPathResolver` → `continueReturned` → `createSemanticReturnedReferences` (`src/domain/javascript/javascriptSemanticReturnedReferences.ts`). That worklist carries the set of initializers already used on the current chain, and reuses an earlier expansion of a `(node, path)` state only when a stored context is a subset of the current one (`contextContains`). In minified React DOM, five bindings (`e`, `l`, `a`, `r`, `n` inside `completeWork`) are reassigned from each other's members many times (`a=ai(e)`, `e=a.updateQueue`, `e=n`, `n=n.sibling`, `r=t.child`, ...). Every distinct ordering of those initializers produces a distinct context, so the memo almost never hits and the walk enumerates simple paths through the alias graph: exponential, mostly short-lived garbage. A CPU profile puts 76% of time in that worklist; the heap profile at exit holds only ~27 MB, so it is allocation churn rather than retention. Because `analyze_web_bundle` runs this in the server process (not the capped worker `analyze_javascript_application` uses), the V8 abort takes the whole MCP server down, which the client sees only as "Connection closed". ### Reproduction from public files only ```sh npm pack react-dom@19.2.8 && tar xzf react-dom-19.2.8.tgz mkdir repro && cp package/cjs/react-dom-client.production.js repro/ printf '<!doctype html><script src="/react-dom-client.production.js"></script>\n' > repro/index.html (cd repro && python3 -m http.server 8765 --bind 127.0.0.1) & chrome --headless=new --remote-debugging-port=9333 --user-data-dir=$(mktemp -d) http://127.0.0.1:8765/ # then call analyze_web_bundle over MCP (or rea analyze-web-bundle) with allowed_origins ["http://127.0.0.1:8765"] ``` Measured through the MCP server (`rea mcp`, `tools/call analyze_web_bundle`): | | Outcome | Time | Peak memory | |---|---|---|---| | 6.7.0 | server killed, SIGABRT, `Ineffective mark-compacts near heap limit` | 70.7 s | 4.75 GB | | 6.7.0 + patch below | result returned, `isError: false` | 5.2 s | 0.71 GB | The same file through `analyzeScript` alone: 6.7.0 aborts even at `--max-old-space-size=4096`; patched finishes in 3.7 s at 361 MB. <details><summary>Cut-down trigger (1.7 KB, from minified React DOM <code>completeWork</code>): 6.7.0 needs ~340 MB for it; the full 6 KB function needs ~1 GB</summary> ```js var __x=function(e,t,n){var r=t.pendingProps;switch(r$(t),t.tag){case 3:return r=null,null!==e&&(r=e.memoizedState.cache),eo(),iy(t),null;case 26:var l=t.type,a=t.memoizedState;return null===e?(ip(t)):a?a!==e.memoizedState?(ip(t),iy(t)):(iy(t)):((e=e.memoizedProps)!==r&&ip(t),iy(t)),null;case 5:if(eu(t),l=t.type)e.memoizedProps!==r&&ip(t);else{if(r1(t))rJ(t);else{switch(a){default:switch(l){case"script":a=a.removeChild(a.firstChild);break;}}}}case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&ip(t);else{if(r1(t)){if(r=null)switch(l.tag){case 5:r=l.memoizedProps}}}case 31:if(n=t.memoizedState){if(r=r1(t)){if(null===e){if(!(e=null!==(e=t.memoizedState)?e.dehydrated:null))throw Error(u(557));}else r2();iy(t),e=!1}else e=!0;}case 13:if(r=t.memoizedState){if(l=r1(t)){if(null===e){if(!(l=null!==(l=t.memoizedState)?l.dehydrated:null))throw Error(u(317));}else r2();iy(t),l=!1}else l=r3(),l=!0;}return n&&(r=t.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool),a=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool)),iy(t),null;case 19:if(null===(a=r.rendering))if(l)iv(r,!1);else{if(0!==uD||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(a=ai(e))){for(t.updateQueue=e=a.updateQueue,e=n;null!==n;)n=n.sibling;}e=e.sibling}}else{if(!l)if(null!==(e=ai(a))){if(l=!0,t.updateQueue=e=e.updateQueue)return iy(t),null}else 2*ey()-r.renderingStartTime>uX&&0x20000000!==n&&(l=!0);r.isBackwards?(t.child=a):(null!==(e=r.last)?e.sibling=a:t.child=a)}if(null!==r.tail){e=r.tail;e:{for(n=e;null!==n;){n=n.sibling}n=!0}}case 24:return null!==e&&(n=e.memoizedState.cache),iy(t),null;}}; ``` </details> ### Suggested fix (tested) Give the returned-reference worklist a budget, following the `getter-expansion` precedent: - Count visited states per callee. Up to `SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT` (4096) behaviour is unchanged. - Past the budget, record a new `returned-reference-expansion` resource limit on `state.valueResourceLimits` and expand each `(node, pathKey)` at most once regardless of context. The per-chain initializer blocking stays, so `n=n.sibling` style self-reassignment still cannot grow paths without end. - Hard stop at 16 × the budget as a final termination guarantee. - Surface it in the bundle result: an unknown with `dimension: "javascript_semantics"` naming the affected scripts, and `completeness.status: "partial"`. Today `analyze_web_bundle` does not carry semantic resource limits into its output at all (that is also true of `getter-expansion`), so a bounded result would otherwise look complete. Validation, all in the same patched 6.7.0 build: - 17 scripts captured from a production Next.js 16.3.8 / React 19.2.8 page, plus the 6 KB function and the 1.7 KB cut-down, each run in a separate process capped at 512 MB. 6.7.0: 2 out of memory, 17 pass. Patched: 19 pass. - Findings (edges, routes, endpoints, WebMCP, inferences) are byte-identical to 6.7.0 for every input 6.7.0 completes. They are also identical for the 6 KB function, comparing against 6.7.0 given an 8 GB heap, even though it crosses the budget. - The budget is reached only by the React DOM chunk (21 callees) and the two cut-downs (1 callee each). None of the 16 ordinary scripts get near it. - End to end on the real page: same two endpoint candidates as `analyze_javascript_application` on the exported scripts, with the React chunk named in the new unknown. Caveats: - 4096 is a judgement, not tuned. - After the budget the walk is context-insensitive, so in principle it can miss returned functions that only a different alias context would reach. That is why it is reported as partial rather than silently. - I patched and tested the `dist/mcp` bundle only. `dist/cli-runtime` carries its own copy, so `rea analyze-web-bundle` on the CLI still aborts until the source change is rebuilt. - Running bundle analysis in the capped worker (the "fail typed, not fatal" part of this issue) is still worth doing on its own; this patch removes the cost blow-up, not the missing isolation. <details><summary>Patch against the published 6.7.0 <code>dist/mcp</code> files (applies with <code>patch -p1</code>; source modules: <code>javascriptSemanticReturnedReferences</code>, <code>javascriptSemanticResourceLimits</code>, <code>javascriptSemanticCoverage</code>, <code>JavaScriptAnalysisWorkerSchemas</code>, <code>webBundleAnalyzerInspection</code>, <code>webBundleAnalyzerResult</code>)</summary> ```diff diff -ru a/dist/mcp/browserObservation-DUraZs62.js b/dist/mcp/browserObservation-DUraZs62.js --- a/dist/mcp/browserObservation-DUraZs62.js 2026-10-24 02:14:20 +++ b/dist/mcp/browserObservation-DUraZs62.js 2026-10-24 02:14:20 @@ -133,6 +133,7 @@ node: endpoint.node, accumulator }); + if (index.bindings.valueResourceLimits.has("returned-reference-expansion")) accumulator.boundedScripts.push(script.script_key); }; inspectNode = (script, node, accumulator, openReceiverFacts, isMapLookup) => { if ((t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) && node.source !== undefined) addEdge({ @@ -346,7 +347,8 @@ visitedNodes: 0, parsedScripts: 0, failedScripts: [], - recoveredScripts: [] + recoveredScripts: [], + boundedScripts: [] }); isIncludedScript = (script) => script.source.included; routeCallNames = [ @@ -421,7 +423,7 @@ const omittedScripts = captureBudgetOmission(inspection, "scripts"); const omittedSourceMaps = sourceMaps.status === "not_requested" ? undefined : captureBudgetOmission(inspection, "source_maps"); const sourceMapIncomplete = sourceMaps.status === "partial" || sourceMaps.status === "unavailable"; - const partial = accumulator.failedScripts.length > 0 || accumulator.recoveredScripts.length > 0 || unavailable.length > 0 || sourceMapIncomplete || omittedScripts !== undefined || omittedSourceMaps !== undefined; + const partial = accumulator.failedScripts.length > 0 || accumulator.recoveredScripts.length > 0 || accumulator.boundedScripts.length > 0 || unavailable.length > 0 || sourceMapIncomplete || omittedScripts !== undefined || omittedSourceMaps !== undefined; return webBundleAnalysisSchema.parse({ capture: buildCaptureObservation(inspection, sourceScripts), observations: { @@ -439,6 +441,7 @@ unavailable, failedScripts: accumulator.failedScripts, recoveredScripts: accumulator.recoveredScripts, + boundedScripts: accumulator.boundedScripts, sourceMaps, omittedScripts, omittedSourceMaps @@ -495,6 +498,11 @@ dimension: "javascript_ast", reason: "The parser recovered from errors in one or more source artifacts; findings may be incomplete", affected_script_keys: [...input.recoveredScripts] + }], + ...input.boundedScripts.length === 0 ? [] : [{ + dimension: "javascript_semantics", + reason: "Returned-reference expansion reached its budget (maximum 4096 context-sensitive states per callee) in one or more source artifacts; alias-derived findings may be incomplete", + affected_script_keys: [...input.boundedScripts] }], ...input.omittedSourceMaps === undefined ? [] : [{ dimension: "source_maps", diff -ru a/dist/mcp/main-Cx-Z7m_n.js b/dist/mcp/main-Cx-Z7m_n.js --- a/dist/mcp/main-Cx-Z7m_n.js 2026-10-24 02:14:20 +++ b/dist/mcp/main-Cx-Z7m_n.js 2026-10-24 02:14:20 @@ -49500,7 +49500,8 @@ "primitive-candidates", "primitive-bytes", "expression-depth", - "getter-expansion" + "getter-expansion", + "returned-reference-expansion" ]); primitive = z.union([ z.string(), diff -ru a/dist/mcp/webSourceLocation-DKIum0vJ.js b/dist/mcp/webSourceLocation-DKIum0vJ.js --- a/dist/mcp/webSourceLocation-DKIum0vJ.js 2026-10-24 02:14:20 +++ b/dist/mcp/webSourceLocation-DKIum0vJ.js 2026-10-24 02:14:20 @@ -1678,18 +1678,20 @@ //#endregion //#region dist/domain/javascript/javascriptSemanticResourceLimits.js -var SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT, SEMANTIC_EXPRESSION_DEPTH_LIMIT, SEMANTIC_GETTER_EXPANSION_LIMIT, SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT, RESOURCE_LIMIT_REASON, jsonStringBytesFromLength, exceedsSemanticPrimitiveStringByteBudget, exceedsSemanticPrimitiveAdditionByteBudget, exceedsSemanticTemplateByteBudget, semanticResourceLimitUnknown, semanticResourceLimitReason; +var SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT, SEMANTIC_EXPRESSION_DEPTH_LIMIT, SEMANTIC_GETTER_EXPANSION_LIMIT, SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT, SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT, RESOURCE_LIMIT_REASON, jsonStringBytesFromLength, exceedsSemanticPrimitiveStringByteBudget, exceedsSemanticPrimitiveAdditionByteBudget, exceedsSemanticTemplateByteBudget, semanticResourceLimitUnknown, semanticResourceLimitReason; function init_javascriptSemanticResourceLimits() { return (init_javascriptSemanticResourceLimits = __esmMin((() => { SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT = 256; SEMANTIC_EXPRESSION_DEPTH_LIMIT = 256; SEMANTIC_GETTER_EXPANSION_LIMIT = 512; + SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT = 4096; SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT = 1048576; RESOURCE_LIMIT_REASON = { "primitive-candidates": "Primitive candidate budget exceeded (maximum 256 alternatives).", "primitive-bytes": "Primitive string-byte budget exceeded (maximum 1048576 estimated JSON bytes).", "expression-depth": "Semantic expression depth budget exceeded (maximum 256 nested expressions).", - "getter-expansion": "Getter expansion was conservatively bounded (maximum 512 scheduled states or a growing receiver cycle)." + "getter-expansion": "Getter expansion was conservatively bounded (maximum 512 scheduled states or a growing receiver cycle).", + "returned-reference-expansion": "Returned-reference expansion was bounded (maximum 4096 context-sensitive states per callee); later states were explored once each regardless of alias context." }; jsonStringBytesFromLength = (codeUnits) => codeUnits * 6 + 2; exceedsSemanticPrimitiveStringByteBudget = (strings) => { @@ -4402,6 +4404,7 @@ function init_javascriptSemanticReturnedReferences() { return (init_javascriptSemanticReturnedReferences = __esmMin((() => { init_javascriptAstValues(); + init_javascriptSemanticResourceLimits(); init_javascriptSemanticCallableResults(); init_javascriptSemanticMutationInitializers(); init_javascriptSemanticPropertyPaths(); @@ -4441,6 +4444,9 @@ parent: null }]; const expanded = new WeakMap(); + const visitedPaths = new WeakMap(); + let visits = 0; + let bounded = false; while (work.length > 0) { const task = work.pop(); if (task === undefined) break; @@ -4451,6 +4457,19 @@ const { item, parent } = task; const node = unwrapJavaScriptExpression(item.node).node; const pathKey = JSON.stringify(item.path); + // Context-sensitive expansion enumerates alias chains, which grows exponentially when several + // bindings are reassigned from each other (minified React DOM). Past the budget, each node and + // path is expanded once, and the bound is reported as a resource limit. + visits += 1; + if (!bounded && visits > SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT) { + bounded = true; + state.valueResourceLimits.add("returned-reference-expansion"); + } + if (visits > SEMANTIC_RETURNED_REFERENCE_EXPANSION_LIMIT * 16) break; + const paths = visitedPaths.get(node) ?? new Set(); + if (bounded && paths.has(pathKey)) continue; + paths.add(pathKey); + visitedPaths.set(node, paths); const covered = expanded.get(node)?.get(pathKey)?.find((context) => contextContains(item.initializers, context)); if (covered !== undefined) { propagateBlockers(covered, parent); @@ -8867,6 +8886,10 @@ }, "getter-expansion": { value: 512, + unit: "items" + }, + "returned-reference-expansion": { + value: 4096, unit: "items" } }; ``` </details>
Author
Owner

@akshatgg commented on GitHub (Oct 24, 2026):

Reproduced on another public page, and measured where main stands now.

Target: TodoMVC React, https://todomvc.com/examples/react/dist/app.bundle.js (236,914 bytes, sha256 6197ad9358985fb3f745aef3fca9abbe2fc7f0cd35cd4525cb8570107ae6b78a). The page also loads base.js (3,752 bytes). macOS arm64, Node 24.22.1, Chrome 155 over CDP.

rea analyze-web-bundle end to end:

Build Result Time Peak RSS
6.7.0 (npm) fatal V8 OOM at the default 4 GiB heap, exit 134, no Evidence ~55 s >4 GiB
main at edc805ec exits 0, completeness.status: "partial", only base.js analyzed; unknown: Owned web analyzer exhausted its max_heap_mb V8 old-space heap of 1024 MiB 12.4 s 1.21 GB
main at edc805ec, --max-heap-mb 4096 exits 0, bundle still unanalyzed; the worker hits the 30 s deadline 32.7 s 2.9 GB
main at edc805ec + #6146's member-mutation change exits 0, both scripts parsed (visited_ast_nodes: 77185), 1 route; partial only for "Local returned-reference projections were conservatively widened" 7.1 s 0.39 GB

So on main the fatal crash is gone (the analysis is now isolated and fails typed), but the bundle still isn't analyzed.

Where it blows up on main: I timed the analyzeScript phases on the same file from source (--max-old-space-size=4096):

Phase main main + #6146
bindings + receivers 0.2 s 0.2 s
indexAmbientWritesSteps 3.2 s, ~76 MiB 3.5 s, ~129 MiB
indexMemberMutationFactsSteps OOM at 4 GiB 2.6 s, ~207 MiB

The returned-reference expansion described above already finishes on main (the alias-selections budget is the only resource limit reported). The remaining blow-up is in member-mutation indexing, which is what #6146 bounds. For the last row I applied only javascriptSemanticMemberMutations.ts and javascriptSemanticResourceLimits.ts from #6146's head (2233deba) onto edc805ec; the CLI restart part was not used. #6146's new settle budget was not reached on this file, so its open "dropped escapes" finding is not exercised here.

<!-- gh-comment-id:6382035032 --> @akshatgg commented on GitHub (Oct 24, 2026): Reproduced on another public page, and measured where `main` stands now. **Target:** TodoMVC React, `https://todomvc.com/examples/react/dist/app.bundle.js` (236,914 bytes, sha256 `6197ad9358985fb3f745aef3fca9abbe2fc7f0cd35cd4525cb8570107ae6b78a`). The page also loads `base.js` (3,752 bytes). macOS arm64, Node 24.22.1, Chrome 155 over CDP. **`rea analyze-web-bundle` end to end:** | Build | Result | Time | Peak RSS | |---|---|---|---| | 6.7.0 (npm) | fatal V8 OOM at the default 4 GiB heap, exit 134, no Evidence | ~55 s | >4 GiB | | `main` at edc805ec | exits 0, `completeness.status: "partial"`, only `base.js` analyzed; unknown: `Owned web analyzer exhausted its max_heap_mb V8 old-space heap of 1024 MiB` | 12.4 s | 1.21 GB | | `main` at edc805ec, `--max-heap-mb 4096` | exits 0, bundle still unanalyzed; the worker hits the 30 s deadline | 32.7 s | 2.9 GB | | `main` at edc805ec + #6146's member-mutation change | exits 0, both scripts parsed (`visited_ast_nodes: 77185`), 1 route; `partial` only for "Local returned-reference projections were conservatively widened" | 7.1 s | 0.39 GB | So on `main` the fatal crash is gone (the analysis is now isolated and fails typed), but the bundle still isn't analyzed. **Where it blows up on `main`:** I timed the `analyzeScript` phases on the same file from source (`--max-old-space-size=4096`): | Phase | `main` | `main` + #6146 | |---|---|---| | bindings + receivers | 0.2 s | 0.2 s | | `indexAmbientWritesSteps` | 3.2 s, ~76 MiB | 3.5 s, ~129 MiB | | `indexMemberMutationFactsSteps` | **OOM at 4 GiB** | 2.6 s, ~207 MiB | The returned-reference expansion described above already finishes on `main` (the `alias-selections` budget is the only resource limit reported). The remaining blow-up is in member-mutation indexing, which is what #6146 bounds. For the last row I applied only `javascriptSemanticMemberMutations.ts` and `javascriptSemanticResourceLimits.ts` from #6146's head (2233deba) onto edc805ec; the CLI restart part was not used. #6146's new settle budget was not reached on this file, so its open "dropped escapes" finding is not exercised here.
Author
Owner

@kaoru0822-kitauji commented on GitHub (Oct 30, 2026):

Measured on current main (317bf3fe2), macOS arm64, Node 26.5.0, without a browser.

Observed: the 1.7 KB cut-down from the first comment, run alone through rea analyze-javascript-application <dir> --format json, still exhausts the analyzer: exit 0, cut.js is analysis_status: "interrupted" with Owned JavaScript worker exhausted its 1024 MiB V8 old-space heap (max_heap_mb) while analyzing cut.js during static, after 282 s wall / 289 s CPU, peak RSS 1.21 GB. No semantic facts are kept for the file.

Where the time goes on main (V8 tick profile of analyzeParsedJavaScriptSemantics on the same file, from source):

  • definitions plus indexAmbientWritesSteps finish in 10 ms, so the returned-reference expansion traced in the first comment is no longer the cost on main.
  • about 74% of ticks are in projectedSourceFactsAtRead → matches (javascriptSemanticProjectedSources.ts), called from markValue → markTarget in javascriptSemanticMemberMutations.ts, which is member-mutation indexing.

With #6146 applied: I applied only #6146's javascriptSemanticMemberMutations.ts and javascriptSemanticResourceLimits.ts hunks onto 317bf3fe2. The same file then finishes in 0.2 s at 210 MB RSS, with coverage.status: "partial" and resourceLimits: ["alias-selections"]. This matches the TodoMVC measurement above.

So on current main this issue comes down to the member-mutation traversal that #6146 bounds. #4716's returned-reference rewrite targets code that main has since replaced. I did not test the full 173 KB Next.js chunk or analyze-web-bundle itself, because that needs a browser.

<!-- gh-comment-id:6516373733 --> @kaoru0822-kitauji commented on GitHub (Oct 30, 2026): Measured on current `main` (317bf3fe2), macOS arm64, Node 26.5.0, without a browser. **Observed:** the 1.7 KB cut-down from the first comment, run alone through `rea analyze-javascript-application <dir> --format json`, still exhausts the analyzer: exit 0, `cut.js` is `analysis_status: "interrupted"` with `Owned JavaScript worker exhausted its 1024 MiB V8 old-space heap (max_heap_mb) while analyzing cut.js during static`, after 282 s wall / 289 s CPU, peak RSS 1.21 GB. No semantic facts are kept for the file. **Where the time goes on `main`** (V8 tick profile of `analyzeParsedJavaScriptSemantics` on the same file, from source): - definitions plus `indexAmbientWritesSteps` finish in 10 ms, so the returned-reference expansion traced in the first comment is no longer the cost on `main`. - about 74% of ticks are in `projectedSourceFactsAtRead` → `matches` (`javascriptSemanticProjectedSources.ts`), called from `markValue` → `markTarget` in `javascriptSemanticMemberMutations.ts`, which is member-mutation indexing. **With #6146 applied:** I applied only #6146's `javascriptSemanticMemberMutations.ts` and `javascriptSemanticResourceLimits.ts` hunks onto 317bf3fe2. The same file then finishes in 0.2 s at 210 MB RSS, with `coverage.status: "partial"` and `resourceLimits: ["alias-selections"]`. This matches the TodoMVC measurement above. So on current `main` this issue comes down to the member-mutation traversal that #6146 bounds. #4716's returned-reference rewrite targets code that `main` has since replaced. I did not test the full 173 KB Next.js chunk or `analyze-web-bundle` itself, because that needs a browser.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3825
No description provided.