[GH-ISSUE #4893] 6.5.0: analyze-javascript-application reports application coverage "partial" (no limits) for any ordinary minified bundle; 6.1.0 reported "complete" for the same input #3037

Open
opened 2026-10-31 00:35:16 +00:00 by bsakaguchi · 9 comments
Owner

Originally created by @yemudo on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4893

Versions: rea-agents 6.5.0 (regression) vs rea-agents 6.1.0 (last known-good for this case). Linux x64, Node 22,
CLI route analyze-javascript-application <dir> --json, defaults plus --max-heap-mb 5120 --analysis-timeout-ms 300000.

Summary: Since the new call/endpoint classification landed, any production-minified JavaScript bundle makes
normalized_result.graph.coverage.status = "partial" with truncated: false and limits: [], even when every file
parses (statistics: parse_failures=0, parsed_javascript_files == inputs) and no resource limit fires. 6.1.0 returns
"complete" for the same input. Consumers that treat application coverage as a completeness signal (as the docs suggest)
now cannot distinguish "analyzer skipped something" from "the input is minified".

Minimal reproduction (public bytes only):

  1. mkdir tree && cp node_modules/react-dom/cjs/react-dom-client.production.js tree/ (react-dom 19.x from npm;
    any production-minified library file reproduces it)
  2. rea-agents analyze-javascript-application tree --json
  3. 6.5.0: graph.coverage = {status:"partial", truncated:false, omitted_count:null, limits:[]}; statistics:
    parse_failures=0, parsed_javascript_files=1.
    6.1.0: graph.coverage = {status:"complete", truncated:false, omitted_count:0, limits:[]} for the same tree.

Where the behaviour comes from (read from the installed 6.5.0 dist):

  • dist/domain/javascript/javascriptStaticAnalysis.js:86 — parse_status: parserErrors > 0 || accumulator.unknownFindings > 0 ? "partial" : "complete". The formula is the same in 6.1.0, but 6.5.0 adds
    dist/domain/javascript/javascriptStaticAnalysisCalls.js (not present in 6.1.0), which increments
    accumulator.unknownFindings for unresolvable call/endpoint targets. A minified bundle always contains thousands of
    these (ambiguous-target / dynamic-call style unknowns), so parse_status becomes "partial" although the parse
    completed — the field now conflates "parsed, with unclassifiable findings" with "incomplete parse".
  • dist/application/javascript/JavaScriptArtifactGraphContext.js:114-116 — new per-observation coverage
    (javascriptAnalysisCoverage) stamps every observation from such a file partial([], null).
  • dist/application/javascript/JavaScriptArtifactGraphBuilder.js:105 and :115 — graphCoverage()'s unknownGap
    includes partialJavaScript = files.some(parse_status === "partial"), and at :138 returns
    partialApplicationCoverage([], null): application-level "partial" with no limit entry explaining why.
    Net effect: there is no CLI option that yields "complete" for minified input — heap/timeout are not the binding
    constraint (no limits fire), and source selection only narrows scope. Expected: either parse_status should stay
    "complete" when the parse succeeded and only finding classification is ambiguous (track that in the unknowns frontier,
    which already exists), or unknownGap should not treat it as an application-coverage gap, or the partial should carry
    a named limit so consumers can tell this case apart.

Separate schema note: graph.coverage.omitted_count changed from 0 (6.1.0) to null (6.5.0) for the no-omission
case. Guards written as omitted_count != 0 now false-fail on complete results too. If intentional, a release-notes
mention would help.

Happy to provide the full JSON of the react-dom reproduction from both versions on request.

Originally created by @yemudo on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4893 Versions: rea-agents 6.5.0 (regression) vs rea-agents 6.1.0 (last known-good for this case). Linux x64, Node 22, CLI route `analyze-javascript-application <dir> --json`, defaults plus `--max-heap-mb 5120 --analysis-timeout-ms 300000`. Summary: Since the new call/endpoint classification landed, any production-minified JavaScript bundle makes `normalized_result.graph.coverage.status` = "partial" with `truncated: false` and `limits: []`, even when every file parses (statistics: parse_failures=0, parsed_javascript_files == inputs) and no resource limit fires. 6.1.0 returns "complete" for the same input. Consumers that treat application coverage as a completeness signal (as the docs suggest) now cannot distinguish "analyzer skipped something" from "the input is minified". Minimal reproduction (public bytes only): 1. mkdir tree && cp node_modules/react-dom/cjs/react-dom-client.production.js tree/ (react-dom 19.x from npm; any production-minified library file reproduces it) 2. rea-agents analyze-javascript-application tree --json 3. 6.5.0: graph.coverage = {status:"partial", truncated:false, omitted_count:null, limits:[]}; statistics: parse_failures=0, parsed_javascript_files=1. 6.1.0: graph.coverage = {status:"complete", truncated:false, omitted_count:0, limits:[]} for the same tree. Where the behaviour comes from (read from the installed 6.5.0 dist): - dist/domain/javascript/javascriptStaticAnalysis.js:86 — `parse_status: parserErrors > 0 || accumulator.unknownFindings > 0 ? "partial" : "complete"`. The formula is the same in 6.1.0, but 6.5.0 adds dist/domain/javascript/javascriptStaticAnalysisCalls.js (not present in 6.1.0), which increments `accumulator.unknownFindings` for unresolvable call/endpoint targets. A minified bundle always contains thousands of these (ambiguous-target / dynamic-call style unknowns), so `parse_status` becomes "partial" although the parse completed — the field now conflates "parsed, with unclassifiable findings" with "incomplete parse". - dist/application/javascript/JavaScriptArtifactGraphContext.js:114-116 — new per-observation coverage (`javascriptAnalysisCoverage`) stamps every observation from such a file `partial([], null)`. - dist/application/javascript/JavaScriptArtifactGraphBuilder.js:105 and :115 — `graphCoverage()`'s `unknownGap` includes `partialJavaScript = files.some(parse_status === "partial")`, and at :138 returns `partialApplicationCoverage([], null)`: application-level "partial" with no limit entry explaining why. Net effect: there is no CLI option that yields "complete" for minified input — heap/timeout are not the binding constraint (no limits fire), and source selection only narrows scope. Expected: either `parse_status` should stay "complete" when the parse succeeded and only finding classification is ambiguous (track that in the unknowns frontier, which already exists), or `unknownGap` should not treat it as an application-coverage gap, or the partial should carry a named limit so consumers can tell this case apart. Separate schema note: `graph.coverage.omitted_count` changed from `0` (6.1.0) to `null` (6.5.0) for the no-omission case. Guards written as `omitted_count != 0` now false-fail on complete results too. If intentional, a release-notes mention would help. Happy to provide the full JSON of the react-dom reproduction from both versions on request.
Author
Owner

@Varshith-Kali commented on GitHub (Oct 17, 2026):

Reproducible analysis — this looks like a regression from the call/endpoint classification change in 6.5.0: classification unknowns now feed parse_status, which then stamps per-observation partial and rolls up into application coverage "partial" with no named limit, even when every file parsed cleanly (parse_failures = 0).

I would like to take this on: separate "parse completed" from "classification ambiguous" (either stop counting call/endpoint unknowns into parse_status, or exclude classification-only partial from the graphCoverage rollup with a named limit entry so consumers can tell the cases apart), plus pin the omitted_count 0-vs-null contract. Happy to have a maintainer point me at the preferred shape before I open the PR.

<!-- gh-comment-id:6241898012 --> @Varshith-Kali commented on GitHub (Oct 17, 2026): Reproducible analysis — this looks like a regression from the call/endpoint classification change in 6.5.0: classification unknowns now feed `parse_status`, which then stamps per-observation `partial` and rolls up into application coverage "partial" with no named limit, even when every file parsed cleanly (`parse_failures = 0`). I would like to take this on: separate "parse completed" from "classification ambiguous" (either stop counting call/endpoint unknowns into `parse_status`, or exclude classification-only partial from the `graphCoverage` rollup with a named limit entry so consumers can tell the cases apart), plus pin the `omitted_count` 0-vs-null contract. Happy to have a maintainer point me at the preferred shape before I open the PR.
Author
Owner

@mertkayacs commented on GitHub (Oct 17, 2026):

I traced where the unknown comes from on a module with no Electron surface. It is the Electron wrapped-call-path expansion in electronWrappedCallPaths.ts, which runs on every module: the probe for contextBridge.exposeInIsolatedWorld evaluates the callee of every call without a literal first argument, and seeded walks evaluate the bodies they reach. Any read the symbolic resolver cannot settle sets its unresolved flag, and that becomes one unknown finding for the file, which is what flips parse_status and then graph coverage.

Since b8fab7429 the resolver treats a read that precedes its initializer in source order as unresolved ("initializer not available at this read"). Instrumenting it on react-dom-client.production.js (react-dom 19.2.0) shows every unresolved read is a hoisted function reading a module-level var initialized later: scheduleTimeout, cancelTimeout, scheduleMicrotask, listener, discreteReplayableEvents. Every production bundle has that shape, which matches "any ordinary minified bundle".

The walk can only emit Electron call paths, and the resolver can only reach an Electron value through a require or import of the Electron module, so on a module without one the unknown describes nothing the analysis could have found. https://github.com/morluto/rea/pull/4915 runs the expansion only for modules that reference Electron. On the trigger module and on the react-dom file the static analysis then reports parse_status: "complete"; Electron modules keep their current unknowns. The omitted_count note is separate and untouched.

<!-- gh-comment-id:6243194142 --> @mertkayacs commented on GitHub (Oct 17, 2026): I traced where the unknown comes from on a module with no Electron surface. It is the Electron wrapped-call-path expansion in `electronWrappedCallPaths.ts`, which runs on every module: the probe for `contextBridge.exposeInIsolatedWorld` evaluates the callee of every call without a literal first argument, and seeded walks evaluate the bodies they reach. Any read the symbolic resolver cannot settle sets its `unresolved` flag, and that becomes one unknown finding for the file, which is what flips `parse_status` and then graph coverage. Since b8fab7429 the resolver treats a read that precedes its initializer in source order as unresolved ("initializer not available at this read"). Instrumenting it on `react-dom-client.production.js` (react-dom 19.2.0) shows every unresolved read is a hoisted function reading a module-level `var` initialized later: `scheduleTimeout`, `cancelTimeout`, `scheduleMicrotask`, `listener`, `discreteReplayableEvents`. Every production bundle has that shape, which matches "any ordinary minified bundle". The walk can only emit Electron call paths, and the resolver can only reach an Electron value through a require or import of the Electron module, so on a module without one the unknown describes nothing the analysis could have found. https://github.com/morluto/rea/pull/4915 runs the expansion only for modules that reference Electron. On the trigger module and on the react-dom file the static analysis then reports `parse_status: "complete"`; Electron modules keep their current unknowns. The `omitted_count` note is separate and untouched.
Author
Owner

@XinwangTao commented on GitHub (Oct 18, 2026):

Confirmed regression with a clear root cause: the new calls analysis folds unresolvable call-target unknowns into the same unknownFindings counter that gates parse_status, so "parsed but with unclassifiable findings" and "incomplete parse" become indistinguishable. That's exactly the kind of conflation that breaks downstream consumers using coverage as a completeness signal.

Agree with the suggested directions — my preference would be keeping parse_status semantic ("complete" when parse_failures=0 and parsing itself finished) and tracking ambiguous call targets separately in the unknowns frontier, so application coverage only degrades for genuine parse incompleteness or fired limits. The partial-with-no-limit shape (partialJavaScript → unknownGap with empty limits) is the worst outcome for consumers: no actionable reason to investigate.

The omitted_count 0→null change deserves a release note at minimum; ideally keep 0 for the no-omission case since omitted_count != 0 guards are a natural way to consume it.

<!-- gh-comment-id:6244219103 --> @XinwangTao commented on GitHub (Oct 18, 2026): Confirmed regression with a clear root cause: the new calls analysis folds unresolvable call-target unknowns into the same `unknownFindings` counter that gates parse_status, so "parsed but with unclassifiable findings" and "incomplete parse" become indistinguishable. That's exactly the kind of conflation that breaks downstream consumers using coverage as a completeness signal. Agree with the suggested directions — my preference would be keeping parse_status semantic ("complete" when parse_failures=0 and parsing itself finished) and tracking ambiguous call targets separately in the unknowns frontier, so application coverage only degrades for genuine parse incompleteness or fired limits. The partial-with-no-limit shape (partialJavaScript → unknownGap with empty limits) is the worst outcome for consumers: no actionable reason to investigate. The omitted_count 0→null change deserves a release note at minimum; ideally keep 0 for the no-omission case since `omitted_count != 0` guards are a natural way to consume it.
Author
Owner

@Varshith-Kali commented on GitHub (Oct 18, 2026):

Update: #4915 is now open with an active fix for this same regression, and it is already picking up review feedback — I will step back from the claim above to avoid duplicate effort. Happy to help review or test #4915 if that is useful.

<!-- gh-comment-id:6244475041 --> @Varshith-Kali commented on GitHub (Oct 18, 2026): Update: #4915 is now open with an active fix for this same regression, and it is already picking up review feedback — I will step back from the claim above to avoid duplicate effort. Happy to help review or test #4915 if that is useful.
Author
Owner

@yemudo commented on GitHub (Oct 18, 2026):

Tested rea-agents-6.6.0 (tag compiled from source; there is no npm package or release asset yet) against the same production React bundle set and the same settings we used for 6.5.0 and 6.1.0. Counts only, no bundle content:

engine application coverage JS files parsed findings semantic nodes
6.1.0 complete 3 of 3 1,843 41,584
6.5.0 partial, no limit named 3 of 3 (as in this issue)
6.6.0 partial, with worker heap/timeout limit entries 1 (only sw.js) 6 1,580

So 6.6.0 does not fix this issue, and for us it is worse than 6.5.0: both application bundles are now skipped (skipped_javascript_files: 2). #4915 is not in the 6.6.0 tag. We stay pinned to 6.1.0 and will retest when #4915 is merged and a package is published. Happy to run #4915 against our bundles if that helps.

<!-- gh-comment-id:6245677860 --> @yemudo commented on GitHub (Oct 18, 2026): Tested `rea-agents-6.6.0` (tag compiled from source; there is no npm package or release asset yet) against the same production React bundle set and the same settings we used for 6.5.0 and 6.1.0. Counts only, no bundle content: | engine | application coverage | JS files parsed | findings | semantic nodes | |---|---|---|---|---| | 6.1.0 | complete | 3 of 3 | 1,843 | 41,584 | | 6.5.0 | partial, no limit named | 3 of 3 | (as in this issue) | | | 6.6.0 | partial, with worker heap/timeout limit entries | 1 (only `sw.js`) | 6 | 1,580 | So 6.6.0 does not fix this issue, and for us it is worse than 6.5.0: both application bundles are now skipped (`skipped_javascript_files: 2`). #4915 is not in the 6.6.0 tag. We stay pinned to 6.1.0 and will retest when #4915 is merged and a package is published. Happy to run #4915 against our bundles if that helps.
Author
Owner

@mertkayacs commented on GitHub (Oct 18, 2026):

Thanks for the 6.6.0 numbers. What you see there has a different cause from this issue. 6.6.0 ships #3852, which reports a file that exhausts the worker heap during static analysis as parse_status: "skipped" with a named limit; 6.5.0 left such files inventoried but unparsed. The heap exhaustion itself on React renderer bundles is a regression from #4243 (merged after 6.5.0) and is tracked in #4348, with the alias-cycle case fixed on main as 91a863c73 (adapted from #4349) and #4363, #4364 and #4716 open for the returned-reference expansion. Your two application bundles would be useful evidence there, the sizes and the exact limit entries in particular.

#4915 changes how call-path unknowns are attributed and leaves resource limits alone, so it will not change which files 6.6.0 skips. If you run it against your set, sw.js is the file where parse_status should go from partial to complete; the two skipped bundles need #4348 first.

<!-- gh-comment-id:6247314613 --> @mertkayacs commented on GitHub (Oct 18, 2026): Thanks for the 6.6.0 numbers. What you see there has a different cause from this issue. 6.6.0 ships #3852, which reports a file that exhausts the worker heap during static analysis as `parse_status: "skipped"` with a named limit; 6.5.0 left such files inventoried but unparsed. The heap exhaustion itself on React renderer bundles is a regression from #4243 (merged after 6.5.0) and is tracked in #4348, with the alias-cycle case fixed on main as 91a863c73 (adapted from #4349) and #4363, #4364 and #4716 open for the returned-reference expansion. Your two application bundles would be useful evidence there, the sizes and the exact limit entries in particular. #4915 changes how call-path unknowns are attributed and leaves resource limits alone, so it will not change which files 6.6.0 skips. If you run it against your set, `sw.js` is the file where `parse_status` should go from partial to complete; the two skipped bundles need #4348 first.
Author
Owner

@XinwangTao commented on GitHub (Oct 18, 2026):

Agreed with the separation: the 6.6.0 skips are the heap regression (#4243/#4348) and #4915 only changes how call-path unknowns are attributed, so neither fix masks the other — that makes it easy for consumers to know which coverage drop to investigate. Also +1 on the schema note: the omitted_count 0→null change should either stay 0 for the no-omission case or be called out in release notes, since omitted_count != 0 is a natural guard for downstream code.

<!-- gh-comment-id:6247419428 --> @XinwangTao commented on GitHub (Oct 18, 2026): Agreed with the separation: the 6.6.0 skips are the heap regression (#4243/#4348) and #4915 only changes how call-path unknowns are attributed, so neither fix masks the other — that makes it easy for consumers to know which coverage drop to investigate. Also +1 on the schema note: the omitted_count 0→null change should either stay 0 for the no-omission case or be called out in release notes, since omitted_count != 0 is a natural guard for downstream code.
Author
Owner

@yemudo commented on GitHub (Oct 18, 2026):

Thanks for the explanation. We re-ran with rea-agents-6.7.0 (tag compiled from source) on the same production React build, same settings as before (node heap 5 GB, --max-heap-mb 5120, --analysis-timeout-ms 300000). Counts and sizes only, as you asked for #4348:

JS file size (bytes) 6.7.0 result
main application bundle 14,642,353 skipped
downtime bundle 395,555 skipped
sw.js small parsed
  • Exact limit entries in the 6.7.0 report: javascript_analysis_worker_heap_limit_bytes=5393874944 and javascript_analysis_worker_timeout_ms=300000.
  • 6.7.0: application coverage partial, 6 findings, 1,580 semantic nodes, run 372 s, report 2.8 MB.
  • 6.1.0 on the same build: coverage complete, all 3 files parsed, 1,843 findings, 41,584 semantic nodes, run 280 s, report 366 MB.
  • 6.7.0 behaves like 6.6.0 for us: #4915 is not in the tag, and both application bundles are still skipped, even the 395 KB one.

We stay on 6.1.0 and will retest when #4348 and #4915 are in a published release. Happy to test a branch build of either fix against these bundles.

<!-- gh-comment-id:6250474892 --> @yemudo commented on GitHub (Oct 18, 2026): Thanks for the explanation. We re-ran with `rea-agents-6.7.0` (tag compiled from source) on the same production React build, same settings as before (node heap 5 GB, `--max-heap-mb 5120`, `--analysis-timeout-ms 300000`). Counts and sizes only, as you asked for #4348: | JS file | size (bytes) | 6.7.0 result | |---|---|---| | main application bundle | 14,642,353 | skipped | | downtime bundle | 395,555 | skipped | | `sw.js` | small | parsed | - Exact limit entries in the 6.7.0 report: `javascript_analysis_worker_heap_limit_bytes=5393874944` and `javascript_analysis_worker_timeout_ms=300000`. - 6.7.0: application coverage partial, 6 findings, 1,580 semantic nodes, run 372 s, report 2.8 MB. - 6.1.0 on the same build: coverage complete, all 3 files parsed, 1,843 findings, 41,584 semantic nodes, run 280 s, report 366 MB. - 6.7.0 behaves like 6.6.0 for us: #4915 is not in the tag, and both application bundles are still skipped, even the 395 KB one. We stay on 6.1.0 and will retest when #4348 and #4915 are in a published release. Happy to test a branch build of either fix against these bundles.
Author
Owner

@mertkayacs commented on GitHub (Oct 18, 2026):

Thanks, that is the data #4348 needs; the two limit entries and the 395 KB case in particular are worth pasting there. I will ping you here when #4915 is on main so you can retest sw.js.

<!-- gh-comment-id:6250782212 --> @mertkayacs commented on GitHub (Oct 18, 2026): Thanks, that is the data #4348 needs; the two limit entries and the 395 KB case in particular are worth pasting there. I will ping you here when #4915 is on main so you can retest sw.js.
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#3037
No description provided.