Skip to content

fix(javascript): bound module source payload before deep semantic analysis - #1610

Closed
akushonkamen wants to merge 2 commits into
morluto:mainfrom
akushonkamen:fix/1563-js-payload-bound
Closed

akushonkamen wants to merge 2 commits into
morluto:mainfrom
akushonkamen:fix/1563-js-payload-bound

Conversation

@akushonkamen

Copy link
Copy Markdown
Contributor

Summary

Deep semantic analysis of one oversized module could expand past the whole host heap and terminate the CLI/MCP process instead of returning a bounded result. Fixes #1563.

Modules above a 2 MiB source-payload budget now skip deep semantics with an explicit failed coverage that carries the named javascript_semantic_module_source_bytes limit and a disclosed limitation. Static analysis, parsing, and every smaller module continue unaffected; coverage stays partial and names the bounding limit, and no semantic absence is claimed for the degraded module.

Problem and expected behavior

The existing semantic resource limits (src/domain/javascript/javascriptSemanticResourceLimits.ts) only bound single primitive values (256 candidates, 1 MiB per byte value); there was no module-payload-level budget. The semantic main path (src/application/javascript/JavaScriptArtifactAnalysis.ts, analyzeArtifactFileSteps → analyzeParsedJavaScriptSemanticsSteps) expanded multi-MiB module sources inside the host heap, so one 4,762,637-byte module (public Claude Code 2.1.296 member chunk-kasbfbhj.js) exhausted a 768 MiB old space and killed the whole process with a fatal OOM before any JSON output. Expected behavior: the process survives, returns partial coverage, and names the limit that bound the result.

Change and scope

  • New SEMANTIC_MODULE_SOURCE_BYTES_LIMIT = 2 MiB payload budget in the JavaScript semantic resource limits.
  • Modules over budget degrade through moduleSourceBudgetJavaScriptSemanticIr() into an explicit failed coverage carrying the named module-source-bytes limit plus a limitation disclosure.
  • Static analysis and all other modules are unaffected; the CLI/MCP process survives; coverage is partial with a named bound.
  • Known cost: on a 2048 MiB heap the affected member previously completed and now deterministically degrades — an intentional trade for a deterministic upper bound.
  • Not included: the general retained-allocation reduction tracked in Improve resource limits and timeout handling for large JavaScript semantic analysis #1565 and the collector-allocation direction discussed by N0zoM1z0 on the issue; no overlap with this change.

Contract and boundary impact

  • Semantic owner and earliest changed stage: JavaScript semantic analysis (domain limits + IR), application orchestration in JavaScriptArtifactAnalysis steps.
  • CLI and MCP/tool-catalog contract: analyze-javascript-application no longer terminates on oversized members; it exits 0 with partial coverage, the named limit and the limitation.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract: the degraded module reports explicit failed coverage with the named limit; its static parse_status stays complete; semantic absence is not claimed.
  • Process execution, authorization, cleanup, or containment impact: no new processes; the process no longer dies by heap OOM.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: 5 new tests (4 in src/domain/javascript/javascriptSemanticResourceLimits.test.ts, 1 module-budget test in src/application/javascript/JavaScriptArtifactAnalysisModuleBudget.test.ts).
  • Base reproduction: the failing member was statically extracted from the SHA512-verified official registry tarball @anthropic-ai/claude-code-linux-x64@2.1.296 (never executed); chunk-kasbfbhj.js, 4,762,637 bytes, SHA256 64516393b8f06dce5b74140781d43b1df4e743de3e283526a533508ca1c58147. Red was re-verified on current main 11e3fae6 after this branch was rebased.
  • User-visible CLI/MCP output: partial coverage with limits=[{"name":"javascript_semantic_module_source_bytes","value":2097152,"unit":"bytes"}] and limitation "Module source-payload budget exceeded (maximum 2097152 bytes); deep semantic analysis was skipped for this module."
  • Remaining proof gaps: the ticket-specified 2048 MiB red variant exited 0 (65 s) on this machine and did not reproduce the OOM; the local OOM boundary sits between 768 and 2048 MiB, consistent with the issue's own 768-fail/1024-pass data. The exact allocations inside the semantic stage remain attributed only to the stage, per the issue's follow-up notes.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • Red, current main 11e3fae6 (rebased base, dist rebuilt): timeout 300 env NODE_OPTIONS=--max-old-space-size=768 node scripts/rea.mjs analyze-javascript-application /tmp/rea1563/rea-claude-memory-repro --json → exit 134, stderr FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory, 0 bytes of JSON output.
  • Green, this branch (dist rebuilt after rebase): same command at 768 MiB → exit 0 in 6.83 s; coverage.status=partial, limits=[{"name":"javascript_semantic_module_source_bytes","value":2097152,"unit":"bytes"}], the budget limitation disclosed; the failing member's static parse_status=complete. At 2048 MiB → exit 0 in 6.39 s with the same deterministic degradation.
  • npx vitest run --project domain --project services src/domain/javascript src/application/javascript → 1024 passed / 0 failed (59 files), including the 5 new tests. (On the earlier acceptance base the same slice was 1023 passed / 6 failed, with the same 6 failures on clean c1b5ccf0; current main has since fixed those 6.)
  • npx vitest run --project adapters src/artifacts src/javascript → 107 passed / 0 failed.
  • npx tsc --noEmit → pass; npx oxlint → 0 errors (627 pre-existing warnings); npx oxfmt --check → pass; npm run verify:module-boundaries → 1411 files pass; git diff --check → clean.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Known behavior change: on larger heaps (e.g. 2048 MiB) the oversized member now deterministically degrades to partial coverage instead of completing.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable — JavaScript-only change; not performed.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the change removes a whole-process termination path and introduces no new process, privilege, or network behavior.

🤖 Generated with Claude Code

…lysis

Deep semantic analysis of one oversized module could expand past the whole
host heap and terminate the CLI/MCP process instead of reporting a bounded
result (issue morluto#1563; public Claude Code 2.1.296 member chunk-kasbfbhj.js,
4762637 bytes, dies with a fatal heap OOM at a 768 MiB old space). The
existing semantic resource limits only bound single primitive values.

Modules above a 2 MiB source-payload budget now skip deep semantics with an
explicit failed coverage carrying the named module-source-bytes limit and a
disclosed limitation, while static analysis, parsing and every smaller
module continue unaffected. Coverage stays partial and names the limiting
bound; no semantic absence is claimed for the degraded module.

Validation: 5 new domain/services tests pass. The JavaScript domain and
services subset (npx vitest run --project domain --project services
src/domain/javascript src/application/javascript) observed 1018 passed /
6 failed (1024 tests, 59 files) on this branch and 1013 passed / 6 failed
(1019 tests, 57 files) on clean c1b5ccf; the same 6 tests fail in both
runs, so they predate this change. The adapters JavaScript and artifacts
slice (npx vitest run --project adapters src/artifacts src/javascript)
passes 107 tests. The public failing member now exits 0 with partial
coverage naming javascript_semantic_module_source_bytes where main exited
134.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@CassieFox23

Copy link
Copy Markdown
Contributor

TL;DR: Fixed: at a 768 MiB heap, the 4.76 MB public module no longer kills the CLI or the MCP server. It returns partial coverage that names the 2 MiB limit. The price: at a 1,024 MiB or default heap, main finished that module and kept far more deep results.

I tested head 808ded3 against main c1217e7 (#1577). Main moved to b22d817 while I ran, so I rebuilt that too. The PR applies cleanly to both. The module is the member from the issue: 4,762,637 bytes, SHA-256 64516393…. It comes from a closed-source CLI package on npm, version 2.1.296. I cut it out with the issue's steps and never ran any of it.

Case main PR
The module, 768 MiB heap, Node 22, 24, 26 Exit 134, out of memory, no output, after 11 to 15 s Exit 0 in 7.7 to 8.9 s. Both graphs partial, limit named. Same bytes on all three Nodes
The module, 1,024 or 2,048 MiB heap Finishes in 87 s or 69 s. Output 193 MB Exit 0 in 8 s. Output 2.9 MB
The module, default heap Finishes in 68 s, peak 2,345 MiB. App graph complete. Semantic graph partial at its 20,000-node ceiling Exit 0 in 8.3 s, peak 1,214 MiB. Both graphs partial, limit named
A file of 2,097,152 bytes (4 styles) Deep analysis runs Same output as main, byte for byte
A file of 2,097,153 bytes (4 styles) Deep analysis runs Deep analysis skipped. Both graphs partial, limit named
6 real inputs Reference 5 match main. The launcher differs, see the bullets
The 4 test files, Node 22, 24, 26 Not applicable 61 tests pass on each. check:fast exits 0
  • Silent loss: none found. Each skipped module showed partial and the named limit. Main's app graph said complete for the same module.
  • Memory: the cap runs after parsing. The PR still parses the module in the process. It passes at 768 MiB and exits 134 at 512 and 384 MiB.
  • Past the cap: a module with partial deep analysis now adds five limitation lines to the semantic graph. No launcher file is over 2 MiB, yet its semantic graph ID and Evidence change. Its status was already partial, and its nodes, relations and coverage are equal. A small file with a duplicate let shows the status flip from unknown to partial.
  • Wording: the top-level limitation says "for this module". It names no path and gives no count. The 12 unknowns name the path.
  • Cost: on the issue's module, the app graph goes from 3,893 module nodes to 499. Import edges go from 7,036 to 1,028, and export edges from 3,054 to 13. Those edges come from the deep pass, so they go too. The parse still says complete.

Not run

  • The full 2,351-file tree. The issue gives no steps to decode its module table. An earlier comment says that tree still failed at 1,024 MiB on the allocation fix.
  • A real minified module of about 2 MiB. My cut from the real member did not parse.
  • Many files, each just under 2 MiB. The cap is per module, so the total has no bound.
  • The module over MCP on Node 22 and 26. The launcher over MCP.
  • Builds on Node 22 and 26. I built on Node 24 only. The tests ran on all three.
  • CI: shards 2 and 3 had failed when I looked. I did not read why.
  • Windows, macOS, and the reporter's own setup.

Thanks, akushonkamen, for the clear root cause, the named limit, and a test that drives both the CLI and MCP.

Heap sweep and memory

Node 24 unless noted. Memory is peak resident size in MiB. Time is wall seconds. Load was 2.9 to 5.3. Load counts programs running or waiting, out of 24 CPU threads. Another test job ran alongside. Each cell is one run.

Heap main PR
384 MiB not run exit 134, 2.6 s, 652 MiB
512 MiB not run exit 134, 4.7 s, 737 MiB
768 MiB exit 134, 11.0 s, 999 MiB exit 0, 7.7 s, 952 MiB
768 MiB, Node 22 exit 134, 15.0 s, 947 MiB exit 0, 8.9 s, 821 MiB
768 MiB, Node 26 exit 134, 11.7 s, 968 MiB exit 0, 7.7 s, 880 MiB
1,024 MiB exit 0, 86.6 s, 1,275 MiB exit 0, 8.3 s, 1,137 MiB
2,048 MiB exit 0, 69.4 s, 1,718 MiB exit 0, 7.7 s, 1,209 MiB
default exit 0, 67.9 s, 2,345 MiB exit 0, 8.3 s, 1,214 MiB

The PR on top of main, at 768 MiB: exit 0, same bytes as the head. The parent 11e3fae at 768 MiB: exit 134. Main b22d817 at 768 MiB: exit 134.

The PR gave the same bytes on every Node and heap (SHA-256 63ad2936…). Main gave the same bytes at 1,024 MiB, 2,048 MiB and default (b0525941…).

Over MCP on Node 24 at 768 MiB, I sent a ping, the analysis, a ping, and a small analysis. Main's server dropped the connection, and every later call failed. The PR's server answered all four, and the analysis result was 2.9 MB.

Boundary files

Each case is a folder with one module file and a small app.js. The module has two imports, two exports and a fetch. Padding fills it to the exact size. Node 24, heap 4,096 MiB.

Style At 2,097,152 bytes At 2,097,153 bytes
Comment padding PR same as main PR partial, limit named
Plain code, one function per line PR same as main (14.3 s, 1,216 MiB) PR 2.2 s, 664 MiB. Main 14.1 s, 1,216 MiB
Minified code, one long line PR same as main (15.5 s, 1,211 MiB) PR 2.3 s, 688 MiB. Main 14.6 s, 1,258 MiB
Two-byte characters PR same as main PR partial, limit named

"Same" means the same output SHA-256. The check counts UTF-8 bytes. The two-byte file at 2,097,153 bytes has only 1,048,654 characters, and it is skipped.

In the comment case at 2,097,153 bytes, the app graph loses edges. Imports go from 4 to 2. Exports go from 3 to 1. Module nodes go from 7 to 3. The fetch endpoint stays. Coverage says partial with javascript_semantic_module_source_bytes.

Plain code just under the cap also passed at a 768 MiB heap: 819 MiB peak. The minified style peaked at 794 MiB.

The limitation texts, as printed:

  • Module source-payload budget exceeded (maximum 2097152 bytes); deep semantic analysis was skipped for this module.
  • Semantic analysis failed for mod.js; no semantic absence claim is available. The module source exceeds the 2097152-byte semantic payload budget; deep semantic analysis was skipped and no semantic absence claim is available for this module. No JavaScript was executed.
Real inputs: IDs and run time

Six inputs, Node 24, heap 4,096 MiB. One is the Beyond All Reason launcher. Its code, spring-launcher, is MIT (LICENSE). Its Flathub build is GPL-2.0-or-later, as its metadata file says. One is a vendor chunk from Hermes Desktop: hermes-agent, MIT. The other four are small files from our scripts. They are the 1,200-function file, the #1495 file with N = 2,000, and two more files with 2,000 functions each. Shape A is exported functions that return objects. Shape B is plain functions that call JSON.stringify.

No input has a JavaScript file over 2 MiB. The launcher has two entries over 2 MiB: a native binary and a source map.

IDs, first 8 to 14 characters. The PR on top of main gave the same bytes as the head on all five.

Input Tree App graph Semantic graph Evidence SHA-256
Launcher main jag_655d199efe jsrg_d777f05cc a5ee5fba
Launcher PR jag_655d199efe jsrg_dc33767de 535f0a7f
Hermes chunk main and PR jag_ff416338f5 jsrg_6b592288e 771fd69b
1,200 file main and PR jag_d3ec319531 jsrg_bae7769cb 29a01447
N = 2,000 file main and PR jag_74fb651f19 jsrg_a9ce0d978 bad7cd29
2,000 functions, shape A main and PR jag_b54736787a jsrg_8b27e7cbf 056667e1
2,000 functions, shape B main and PR jag_2eaae796e5 jsrg_895f83131 8a4a774d

The launcher's semantic graph has the same 100,000 nodes, 93,437 relations and 33,444 unknowns in both trees. It has the same coverage too. Only the limitations differ. The PR adds five lines. One of them says the parser recovered from syntax errors. Main b22d817 gave the same bytes as c1217e7 (#1577) on the five inputs I re-ran. The N = 2,000 file matches my earlier IDs for it on main 11e3fae.

Wall time in seconds, median of 3 interleaved runs. Peak memory in MiB.

Input main PR Peak memory (main / PR)
Launcher 43.1 44.9 1,658 / 2,040
Hermes chunk 7.8 7.5 786 / 790
1,200 file 2.9 2.9 449 / 452
N = 2,000 file 4.3 4.3 554 / 516
2,000 functions, shape A 4.0 4.0 506 / 497
2,000 functions, shape B 5.3 5.3 611 / 614

Load was 1.7 to 4.2 and fell during the run. For the N = 2,000 file it was 1.1 to 1.4. The launcher gap is 4%. The spread between its runs was 4% to 7%. The PR's launcher peak was higher in all 3 runs. I do not know why.

Commands
PATH=$HOME/.local/opt/node24/bin:$PATH
git archive <sha> | tar -x -C <dir>
npm ci && npm run build
npm pack --ignore-scripts <package and version from the issue>
python3 -I slice.py <tgz> <outdir>
NODE_OPTIONS=--max-old-space-size=768 node scripts/rea.mjs analyze-javascript-application <dir> --artifact-format directory --json
/usr/bin/time -f "%e %M" <the command above>
npm run test:focused -- <the 4 test files>
npm run check:fast

slice.py is the issue's Python, with both SHA-256 checks. Both pass. The PR on top of main came from git diff <parent> <head> and patch -p1. The npm commands ran with a fake home folder and HUSKY=0.

Raw output

Tests, Node 22.23.1, 24.18.0 and 26.11.1, on the PR head: 4 files and 61 tests passed each time.

npm run check:fast on a clean clone of 808ded3: typecheck and lint, 2 of 2 tasks, exit 0.

Main, 768 MiB, Node 24, end of stderr:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Node 22 prints Reached heap limit Allocation failed instead.

PR, same command, coverage in the JSON:

graph.coverage:          status partial, limits [javascript_semantic_module_source_bytes = 2097152 bytes]
semantic_graph.coverage: status partial, truncated true, limits [same]
Test setup

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

@N0zoM1z0 N0zoM1z0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review of exact head 808ded32c4b18691365ca5fcc54c090621c92c25, built and exercised on Linux x64 with Node 24.18.0/npm 11.16.0. The explicit failed-module coverage, global unknown frontiers and preserved static-fact intent are useful, but the resource boundary does not yet protect the real CLI/MCP host. Requesting changes for the remaining #1563 termination path.

Actual public workflow evidence (CPUs 13/15, nice 10, one heavy lane, 2304 MiB process-family RSS guard, 2 GiB host reserve; no guard stop in these runs):

  1. Original complete extracted Claude Code 2.1.296 directory, 2351 relevant files, NODE_OPTIONS=--max-old-space-size=768 node scripts/rea.mjs analyze-javascript-application INPUT --json: exit 134, fatal heap exhaustion at chunk-kasbfbhj.js (1432/2351), zero CLI stdout, 170.64 seconds; peak family RSS 1,165,242,368 bytes. Same official bytes as #1563; the 4,762,637-byte member's SHA256 is 64516393b8f06dce5b74140781d43b1df4e743de3e283526a533508ca1c58147. The isolated-member green in the PR does not establish complete-directory recovery.
  2. Dense input below the new cutoff: function f(){return 1;}\n plus 'f();'.repeat(400000), UTF-8 1,600,024 bytes, SHA256 fd861fc0c984035c6c9a41df7bf389f6e82a69404f7bf91323159e0aa56f6efe. Actual CLI at 768 MiB: exit 134, fatal heap exhaustion, zero stdout, 34.44 seconds; peak family RSS 969,363,456 bytes.
  3. Real pinned-SDK stdio MCP on that same below-cutoff input: complete 170-tool catalog and initial ping succeed; analysis fails CONNECTION_CLOSED, fatal heap exhaustion is captured; subsequent ping and a separate small analysis both fail Not connected. Server 768 MiB/parent 512 MiB; peak family RSS 1,297,494,016 bytes. Source SHA unchanged.
  4. Real pinned-SDK MCP on the original complete directory: the 170-tool catalog and initial ping succeed, then analysis fails CONNECTION_CLOSED after 174.60 seconds with fatal heap exhaustion. Subsequent ping and a separate small analysis both fail Not connected. Server 768 MiB/parent 512 MiB; peak family RSS 1,411,112,960 bytes, no guard stop. Culprit source SHA unchanged.
  5. Positive control: a 2,097,199-byte file made of an inert comment plus export const answer=42; fetch("/answer") returns explicit partial coverage with the named limit and no semantic bindings. Subsequent ping and small analysis succeed; source SHA unchanged. This verifies the disclosed skip policy, rather than a bound on parsing/semantic expansion.

The source-byte check runs after parsing, open-receiver classification and static analysis, and does not bound accumulated representations from earlier modules. Please use a supervised, genuinely bounded analysis boundary/admission policy that preserves observations and returns an actionable resource failure while keeping the parent alive. Moving the same fixed source check earlier or lowering it does not resolve the below-cutoff expansion counterexample. Keep #1563 open until the full original workflow and connection recovery are verified; adjust the deterministic-upper-bound/process-survival claims accordingly.

No new tools, provider installations or target execution were required. macOS/Windows, real Hopper/Ghidra and higher-heap full-tree behavior were not verified in this review. CPU sampled counters are not used as performance evidence.

const semantics =
analysis.parse_status === "complete" || analysis.parse_status === "partial"
? yield* analyzeParsedJavaScriptSemanticsSteps(parsed)
? exceedsSemanticModuleSourceBytesBudget(file.text.value)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Contain parsing and accumulated analysis before claiming host survival

The new check runs after Babel parsing, receiver classification and static analysis, and is a per-file source-length cutoff rather than a bound on expanded/retained representations. On this exact head, the original full Claude Code directory still exits 134 at chunk-kasbfbhj.js (1432/2351), zero CLI stdout, with a 768 MiB old space; the real 170-tool stdio MCP server also dies with CONNECTION_CLOSED and subsequent ping/small analysis fail Not connected. No external RSS/time guard stopped either run. A 1,600,024-byte module (function f(){return 1;}\n + f(); repeated 400000 times) is below this limit and independently kills both public workflows too. Thus isolated-member success cannot close #1563 or establish the stated deterministic resource bound. Please supervise/bound the actual parsing and analysis work plus retained representations, preserve observed facts, and verify full-directory failure recovery and the below-cutoff case before making process-survival claims.

@N0zoM1z0 N0zoM1z0 added area: cli CLI adapter and one-shot commands area: domain Domain logic, errors, and pure functions area: mcp MCP server, transport, and tool registration bug Something isn't working performance Performance regressions, scaling limits, and bounded-resource work labels Oct 10, 2026
@CassieFox23

Copy link
Copy Markdown
Contributor

TL;DR: Byte size alone does not decide this. Files with few syntax nodes per byte pass at the full 2 MiB cutoff. The file in the review has over four times more nodes per byte, and it runs out of memory at 768 MiB. Rebasing onto current main moves the threshold, but dense files still die.

My earlier comment found boundary files of exactly 2,097,152 bytes passing at 768 MiB. The review's file is smaller and fails. Both are true because the files have different shapes. I rebuilt the review's file (function f(){return 1;} plus 400,000 copies of f();; its SHA-256 matches the review) and varied size and shape. The head is still 808ded3.

Module Nodes per KiB Head 808ded3 Main c12a532 Main plus #1610
125,000 calls, 500,024 bytes 768 Exit 0, 12 s, 845 not run not run
250,000 calls, 1,000,024 bytes 768 Exit 0, 17 s, 912 not run not run
325,000 calls, 1,300,024 bytes 768 Exit 0, 20 s, 931 not run not run
400,000 calls, 1,600,024 bytes (the review's file) 768 Exit 134, 11 s, 929 Exit 0, 24 s, 936 Exit 0, 25 s, 953
500,000 calls, 2,000,024 bytes 768 Exit 134, 15 s, 1,048 not run Exit 134, 15 s, 1,033
524,282 calls, 2,097,152 bytes 768 not run Exit 134, 15 s, 1,013 Exit 134, 15 s, 989
266,669 sums, 1,600,023 bytes 1,024 Exit 134, 11 s, 947 Exit 0, then exit 134 Exit 134, 11 s, 1,000
16,386 short functions, 1,600,020 bytes 168 Exit 0, 13 s, 821 not run not run
One string literal, 1,600,025 bytes 0 Exit 0, 1 s, 308 not run not run

Each cell is one run: seconds, then peak memory in MiB. A "call" is f(); and a "sum" is x=1+2;. Exit 134 means the heap ran out. Peak memory can pass 768 MiB, because that limit covers only the JavaScript heap. Times are rough, since other tests ran on the box (load average 1.3 to 4.3, out of 24 threads).

This agrees with the review: a per-module byte cap does not bound memory. The rows for main + #1610 show dense files under or at the cap that still abort.

Not run

Commands
# input: a directory holding only mod.js, default --artifact-format (auto)
# the review does not say how the file was packaged
env -i HOME=/tmp/p1610b/home PATH=$HOME/.local/opt/node24/bin:/usr/bin:/bin \
  TMPDIR=/tmp/p1610b/tmp NODE_OPTIONS=--max-old-space-size=768 \
  nice -n 19 /usr/bin/time -f "%e %M" timeout 600 \
  node scripts/rea.mjs analyze-javascript-application <dir> --json
# "main + #1610" is main with the diff 11e3fae69..808ded32c applied by patch -p1 (0 rejects)
# nodes per KiB: @babel/parser, sourceType unambiguous, errorRecovery on, every AST node counted
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Head: 808ded3
  • Base: 11e3fae
  • Main: c12a532

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

@morluto

morluto commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Closing as superseded by merged #1651, commit 4999e4b. Source parsing and semantic projection now run in an owned worker with independent heap/deadline controls, validated static checkpoints, explicit partial coverage, and retained Evidence through CLI/MCP. This addresses the host-termination boundary tracked in #1563 without imposing this PR's fixed 2 MiB source cutoff. The below-cutoff dense-source and full-directory counterexamples recorded in the review are covered by the replacement approach. Thank you for the reproduction and proposed fix; this branch/history is retained.

@akushonkamen

Copy link
Copy Markdown
Contributor Author

Thanks @morluto — agreed, and thanks for carrying this into #1651. The worker-based isolation with independent heap/deadline controls addresses the #1563 host-termination boundary more robustly than this PR's fixed 2 MiB byte cutoff, which — as the reviews above showed — dense low-nodes-per-byte sources could still defeat. Treating this branch as superseded; no further work here.

@akushonkamen
akushonkamen deleted the fix/1563-js-payload-bound branch October 25, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli CLI adapter and one-shot commands area: domain Domain logic, errors, and pure functions area: mcp MCP server, transport, and tool registration bug Something isn't working performance Performance regressions, scaling limits, and bounded-resource work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(javascript): public Bun module exhausts bounded heap and terminates CLI/MCP

5 participants