Skip to content

fix(bun): report the real runtime and enforce worker memory budgets on Windows - #1730

Merged
morluto merged 2 commits into
morluto:mainfrom
xeropresence:fix/bun-runtime-limits
Oct 11, 2026
Merged

morluto merged 2 commits into
morluto:mainfrom
xeropresence:fix/bun-runtime-limits

Conversation

@xeropresence

Copy link
Copy Markdown
Contributor

Summary

When REA runs on Bun, it describes a Node.js/V8 runtime it did not observe, and its JavaScript helper processes run with no memory bound. Bun reports process.versions.node (its Node.js compatibility version, e.g. 26.3.0) and V8-shaped getHeapStatistics() values, but it is not V8 and ignores V8 flags such as --max-old-space-size. REA relies on those flags to bound three helpers it starts on process.execPath: the JavaScript source analyzer (--max-old-space-size=<max_heap_mb>, default 1024), the source-map codec (192 MiB) and the HAR decoder (192 MiB old generation).

This PR:

  • Identifies the runtime once from process.versions (only Bun sets process.versions.bun) and records it truthfully. Under Bun, trace_web_source_location now works instead of always failing, and Evidence reports unenforced V8 heap limits as null (or omits them) instead of inventing numbers. rea doctor says it is running on Bun.
  • On Windows under Bun, gives each helper's owned Job Object a commit limit of its heap budget plus a 512 MiB Bun runtime reserve, and reports both the limit and reaching it. Reaching it returns the same memory resource_constraint failure Node's V8 out-of-memory path returns.

Node.js launches and outputs are unchanged, apart from a new details object on the doctor node check.

No issue is linked; this was found while running REA under Bun on Windows.

Problem and expected behavior

Smallest triggers, all under Bun 1.4.2 on Windows x64:

  1. rea trace-web-source-location <manifest> 0 <map> <url> 1 0 --json always failed. The codec reported runtime: { id: "node-v8", version: process.version, v8_heap_limit_bytes: getHeapStatistics().heap_size_limit }. Bun's synthetic heap limit (~470 MB) is above the decoder's 256 MiB check, so the decoder returned AnalysisOutputError: Codec reported a V8 heap limit exceeding its independent process budget. Even had it passed, the Evidence would claim a V8 runtime and an enforced heap limit that did not exist.
  2. JavaScript analysis resource failures put worker_old_space_mb and worker_heap_limit_bytes (Bun's heap_size_limit) into reported_limits and coverage limits. Bun enforces neither.
  3. inspect_web_network_capture limitations said "HAR uses a 192 MiB Node old-generation heap", and HAR memory failures reported old_generation_heap_mib: 192. Bun does not enforce that heap.
  4. rea doctor --json reported {"name":"node","detail":"26.3.0"} with no sign that the runtime was Bun.
  5. The helpers were unbounded. Uncapped measurements: analyzing a 1.7 MB bundle (@vue/compiler-sfc browser build) committed up to 50,537 MiB and then failed with a generic ProviderAdapterError. A 34,098-entry HAR, which exhausts the 192 MiB heap on Node.js, committed 1,026 MiB.

Expected: REA records the runtime it actually ran on, reports an unenforced limit as unavailable rather than as a number, and bounds each helper by the memory budget it already declares wherever the platform allows it.

Change and scope

Runtime identity

  • src/domain/runtimeVersion.ts: adds JavaScriptRuntime and javaScriptRuntime(process.versions), the single place that decides between Node.js and Bun. Adapters pass process.versions; the domain module does not read process.

  • Source-map trace (TraceSourceMap.ts, webSourceLocation.ts, SourceMapDecoder.ts, WebSourceLocationService.ts): runtime is a discriminated union:

    • { id: "node-v8", version, v8_heap_limit_bytes: <int> } (unchanged on Node.js)
    • { id: "bun", version, node_compatibility_version, v8_heap_limit_bytes: null, process_memory_limit_bytes: <int> | null }

    The decoder applies the 256 MiB check only to V8 reports. It rejects a report whose runtime differs from the runtime that launched the codec (the codec always runs on process.execPath; without this check a bun report would bypass the heap check), and a Bun report whose process_memory_limit_bytes differs from the limit its launcher enforced. The final limitation states the budget that actually applied.

  • JavaScript analysis worker: the ready handshake's heap_limit_bytes is nullable; the worker server sends null under Bun and the parent rejects a handshake that contradicts its own runtime. Under Bun, worker_heap_limit_bytes is null and worker_old_space_mb is not reported. javaScriptAnalysisCoverageLimits already drops non-numeric values, so coverage omits the heap limit.

  • HAR decoder (CaptureRelease.ts, CaptureFailures.ts, HistoricalCaptureDecoder.ts): HAR_CAPTURE_HEAP_ENFORCED gates the heap sizes in limitations, reported_limits and remediation.

  • Doctor: DoctorHost.nodeVersion is replaced by runtime: JavaScriptRuntime. The node check keeps its name, because DoctorScope and scripts/verify-package-discovery.mjs select it by name. It carries structured details: { runtime: "node", version }, or { runtime: "bun", version, node_compatibility_version } with detail Bun 1.4.2 (Node.js 26.3.0 compatibility). Under Bun the check passes when the Node.js compatibility version satisfies SUPPORTED_NODE_VERSION_RANGE, the same version setup already gates on (SetupHost uses process.versions.node); the Windows native addon already supports Bun as a Node-API host (fix(windows): load native controls in Node-API hosts not named node.exe #1531).

Audit of src for process.version, process.versions, node-v8, getHeapStatistics, heap_size_limit, nodeVersion and max-old-space-size:

Location Kind Action
TraceSourceMap.ts runtime record; SourceMapDecoder.ts heap check Evidence claim plus a check that depends on it Fixed
WebSourceLocationService.ts limitation "192 MiB old-generation heap and one V8 worker" Evidence claim Fixed
JavaScriptAnalysisWorkerServer.ts ready heap_limit_bytes; JavaScriptAnalysisWorker.ts #limits() Output claim (error details, coverage limits) Fixed
CaptureFailures.ts, HistoricalCaptureDecoder.ts HAR heap sizes Output claim (limitations, error details, remediation) Fixed
Doctor.ts / DoctorDiagnostics.ts nodeVersion Output claim Fixed
JavaScriptAnalysisWorker.ts parent result budget (heap_size_limit - heapUsed), checkpoint admission; JavaScriptAnalysisWorkerServer.ts GC trigger at half heap_size_limit Internal admission math Unchanged. Under Bun these use Bun's reported statistics, which describe no enforced limit. parent_result_budget_bytes is a derived admission budget, not a runtime claim, so it is still reported.
JsonInputFile.ts native-parse choice and assembly admission (total_available_size) Internal admission math; error details echo its inputs Unchanged. Under Bun total_available_size is ~33.6 GB, so these refusals are practically unreachable there.
--max-old-space-size etc. passed to the codec, HAR decoder, EVM worker and analysis worker Process arguments Unchanged. Under Bun on Windows the Job Object limit below bounds the codec, HAR decoder and analyzer instead. The EVM worker keeps its Linux prlimit cap and does not report its 256 MiB value.
NativeModuleResolver.ts --js-flags=--max-old-space-size=128 Chromium flag Unrelated to the host runtime
JadxSession.ts heap_limit_mib; WindowsNativeLoader.ts process.versions.napi JVM value; Node-API admission Unrelated

Job Object memory limit (Windows, Bun only)

Native addon (native/windows/src/process.cc, authority.hpp):

  • process_spawn accepts an optional 6th argument: the job memory limit in bytes. It must be a positive safe integer and a whole number of pages; anything else fails with ERROR_INVALID_PARAMETER before any process is created. Existing 4- and 5-argument calls are unchanged.
  • createJob(limit) sets JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_JOB_MEMORY and JobMemoryLimit, then reads the policy back and requires the exact flags and exact limit. The page-alignment rule exists so the read-back can be exact, because the kernel stores the limit in pages.
  • For a limited job, a private I/O completion port is associated with the job before CreateProcessW. Each process_poll drains it without waiting and records a sticky memoryLimitReached when JOB_OBJECT_MSG_JOB_MEMORY_LIMIT (10) arrives. The drain runs before the exit observation, so an exit caused by a failed allocation is never reported without the notification. A limited job's poll also returns peakJobMemoryBytes (PeakJobMemoryUsed). An unlimited job returns neither field, so the poll shape for existing launches is unchanged. The spawn result echoes jobMemoryLimitBytes. process_close releases the port.
  • inspect() creates a 64 MiB-limited probe job with a port, then reports jobMemoryLimit: true.

TypeScript plumbing:

  • WindowsNativeLoader: jobMemoryLimit: z.literal(true).optional(); optional because artifacts built before this change omit it.
  • WindowsOwnedProcess: the trailing options are one object { protocolStdin, memoryLimitBytes, authority }; the only other caller (scripts/lib/windows-protocol-input.mjs) is migrated. A requested limit fails closed: an artifact without jobMemoryLimit refuses the launch instead of starting an unlimited process. The spawn read-back must equal the request. A limited job's poll is parsed with a strict schema that requires both new fields, and an unlimited job's poll rejects them. memoryLimitReached is set before exit is emitted.
  • ProviderProcess: OwnedProviderProcessSpawnOptions.memoryLimitBytes is honored only by the Windows owned-job launcher; every other launcher throws instead of ignoring it. memoryLimitReached is passed through to ProviderProcessSnapshot, and runOwnedCommand carries it via the spawn options.

Policy (src/process/JavaScriptWorkerMemoryLimit.ts, the single place this is decided): javaScriptWorkerMemoryLimitBytes(heapBudgetMiB) returns heapBudgetMiB × 1 MiB + BUN_WORKER_RUNTIME_RESERVE_BYTES only when platform === "win32" and the runtime is Bun; otherwise null. Every call site spreads memoryLimitBytes and adds the new reporting fields only when it is non-null, so Node.js launch arguments, the native call, the poll schema, Evidence and limitations are unchanged by construction.

Reserve derivation (BUN_WORKER_RUNTIME_RESERVE_BYTES = 512 MiB): bun -e 1 peaks at 52 MiB of job commit, but REA's workers commit far more before doing real work, because JavaScriptCore commits parsed and compiled module code. Peak job commit on trivial inputs, 5–8 uncapped runs each, on a 32-logical-processor host:

Worker Peak job commit (trivial input)
Source-map codec 278–368 MiB
HAR decoder 300–345 MiB
Analysis worker 321–346 MiB

512 MiB covers the largest observed baseline with about 40% margin. Resulting limits: 704 MiB for the codec and HAR decoder, and max_heap_mb + 512 MiB for the analyzer (1536 MiB by default).

Classification relies on the job notification, not on exit status, because Bun reports a reached limit in several ways: exit 3 (panic), exit 1 (RangeError: Out of memory), or 0xC0000409 (observed for the HAR decoder).

Helper Result when the limit is reached Reported limit
Analyzer AnalysisResourceConstraintError(…, "memory", …) from every path (exit, broken pipe or truncated frame, worker-caught exception, post-loop check). Completed static facts are retained, and the next source starts a fresh worker. worker_heap_limit_bytes: null plus worker_process_memory_limit_bytes, which is also a coverage limit
Source-map codec Memory AnalysisResourceConstraintError; no partial result codec_process_memory_limit_bytes; Evidence runtime.process_memory_limit_bytes
HAR decoder captureMemoryFailure from both the failed-exit path and a success path whose job reached the limit process_memory_limit_bytes, plus a remediation sentence

Not included: memory enforcement for Bun on Linux and macOS (they keep null / no-limit reporting), EVM (already prlimit-capped), Bun support in installers or rea setup/rea update, and changes to the rea doctor remediation wording.

Docs: installation.md, web-source-location.md, javascript-artifact-reconstruction.md, web-network-captures.md, testing.md, native/windows/README.md, and the skill reference javascript-applications.md.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/domain/runtimeVersion.ts (runtime identity) and src/process/JavaScriptWorkerMemoryLimit.ts (limit policy); the native process_spawn/process_poll boundary, then WindowsOwnedProcess, then spawnOwnedProviderProcess. Producers: codec process, analysis worker server, HAR adapter, doctor host.
  • CLI and MCP/tool-catalog contract: the trace_web_source_location output schema's normalized_result.runtime changes from { id: string, version: string, v8_heap_limit_bytes: int } to the two-variant union above; Node.js output is byte-identical. The max_heap_mb / --max-heap-mb description states that Bun ignores V8 heap flags and that Windows limits committed memory instead. rea doctor --json's node check gains details; on Node.js, detail is unchanged.
  • Provider, bridge, target-format, or platform compatibility: Node.js behavior is unchanged. Under Bun, trace_web_source_location works instead of always failing. Under Bun on Windows the helpers require the matching native artifact; an older artifact refuses the launch with "does not support Job Object memory limits" instead of running unlimited.
  • Evidence, artifact, provenance, or reconstruction contract: source-map Evidence records the runtime that ran the codec. Unenforced heap limits are null or omitted instead of fabricated. Enforced Job Object limits are reported (worker_process_memory_limit_bytes, codec_process_memory_limit_bytes, process_memory_limit_bytes) and limitations state which budget applied.
  • Process execution, authorization, cleanup, or containment impact: on Windows under Bun, the helpers' owned jobs gain a commit limit and a private completion port whose lifetime matches the job's. Kill-on-close, breakaway denial and atomic job assignment are unchanged and still verified. New consistency checks reject codec and worker reports from a runtime other than the one REA launched, and codec reports of a limit other than the enforced one.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: ignored catalogs and skills/ regenerate from the changed schema, description and skill reference; no committed generated files changed (docs:check:built and mcp-catalog:check:built pass). native/windows/build is rebuilt by CI.

Evidence and regression coverage

  • Tests added or updated:

    Test file What it covers
    src/domain/runtimeVersion.test.ts Identifies Node.js and Bun from process.versions-shaped input.
    src/application/Doctor.test.ts node check details for Node.js; Bun passes with a supported compatibility version (26.3.0) and fails with an unsupported one (25.1.0). Doctor.fixture.ts and GhidraDoctor.test.ts move to the runtime host field.
    src/process/JavaScriptWorkerMemoryLimit.test.ts Policy: Bun+win32 → 704 MiB / 1536 MiB; Node.js on win32 and Linux → none; Bun on Linux and macOS → none.
    src/windows/WindowsOwnedProcess.test.ts Refuses a limit without the capability, before any native call; unlimited launch keeps 4 arguments and rejects unrequested limit fields; limited launch passes [false, limit], verifies the read-back and sets memoryLimitReached before exit; rejects a limited poll missing the fields and a mismatched applied limit.
    src/windows/WindowsNativeLoader.test.ts jobMemoryLimit admitted as true, absent on earlier artifacts, false rejected.
    src/process/ProviderProcessOutput.test.ts Snapshots carry memoryLimitReached only when the handle observed it.
    src/browser/history/CaptureFailures.test.ts Heap sizes in reported_limits only on Node.js; a reached limit after a Bun panic (exit 3) or exception (exit 1) → memory resource_constraint with process_memory_limit_bytes; the same exits without the notification stay execution_failure.
    src/application/WebSourceLocationService.test.ts Limitation sentence for Node V8, Bun with a Job Object limit, and Bun without one.
    tests/boundary/process/providerProcess.test.ts A non-Windows launcher refuses a limit.
    tests/boundary/javascript/sourceMapDecoder.test.ts The real codec's runtime record for whichever runtime runs the test; replaying the real report succeeds and the other runtime's record is rejected; classification by the notification; a mismatched reported limit is rejected.
    tests/boundary/javascript/javascriptSourceAnalysisWorker.test.ts Heap limits reported only when V8 enforces them; a contradicting handshake is rejected; classification (memory vs. ProviderAdapterError) by the notification, using a real child.
    tests/acceptance/webSourceLocationCli.test.ts The real CLI/MCP run expects the runtime id of the executing runtime.
    tests/acceptance/tools/historicalCaptureResources.test.ts Node keeps the fixed-V8-heap expectations; Bun on Windows expects the Job Object failure; Bun elsewhere is skipped because no bound exists to reach.
    scripts/verify-windows-native.mjs + scripts/lib/windows-job-memory-limit.mjs Native conformance (CI, Node.js on windows-latest), listed below.

    scripts/lib/windows-job-memory-limit.mjs asserts: inspect().jobMemoryLimit === true; invalid limits (0, negative, fractional, 2**53, NaN, Infinity, non-page-aligned 4097, a string, null) are rejected with Win32 87; an unlimited poll has neither new field; a 128 MiB-limited child allocating external Buffer memory (which V8 flags cannot bound) exits nonzero without completing, reports memoryLimitReached, reaches activeProcesses === 0, and peaks within [limit − 64 MiB, limit + 32 MiB]; a control child under the same limit exits 0 without reaching it; the WindowsOwnedProcess wrapper reports the reached limit; a missing capability is refused. Measured peaks exceed the limit by a few MiB of kernel-charged commit (512→520, 256→262, 704→708 MiB), hence the tolerance.

  • Base reproduction or other evidence:

    • On base under Bun, three tests fail and pass with this change (see Validation): the real-codec sourceMapDecoder test (Codec reported a V8 heap limit exceeding its independent process budget.), webSourceLocationCli (exit 1), and the HAR exhaustion acceptance test (nothing bounded the decoder).

    • Throwaway probes outside the repo (a PowerShell P/Invoke job probe and a Bun FFI job helper), Bun 1.4.2: with a 512 MiB job limit Bun panicked (exit 3, peak 520 MiB); with 256 MiB it threw a JavaScript out-of-memory exception (exit 1, peak 262 MiB). In both cases the completion port received JOB_OBJECT_MSG_JOB_MEMORY_LIMIT (10), the runtime-independent signal this change relies on.

    • Before the native control was built, this change's TypeScript was run with a launcher that put the child into a nested FFI Job Object with the requested limit:

      Run Limit Peak Result
      Analyzer, 1.7 MB bundle 1536 MiB 1538.5 MiB Memory AnalysisResourceConstraintError, static facts retained
      Analyzer, next small file 1536 MiB 345 MiB Succeeded in a fresh worker
      HAR, 34,098 entries 704 MiB 707.8 MiB Exit 0xC0000409; memory resource_constraint with process_memory_limit_bytes: 738197504
      HAR, 2 entries 704 MiB 320 MiB Succeeded
      Codec, at its maximum input budgets 704 MiB 405 MiB Stopped at its 262144-record structural budget; the limit was never reached
      Codec, same input 300 MiB (applied on purpose) — Memory AnalysisResourceConstraintError
  • User-visible CLI/MCP output (Bun 1.4.2, Windows x64, built CLI with the CI-built native artifact):

    • bun scripts\rea.mjs doctor --json, node check:
      { "name": "node", "ok": true, "classification": "healthy",
        "detail": "Bun 1.4.2 (Node.js 26.3.0 compatibility)",
        "details": { "runtime": "bun", "version": "1.4.2", "node_compatibility_version": "26.3.0" } }
    • bun scripts\rea.mjs trace-web-source-location <manifest> 0 <map> "https://app.test/app.js.map?v=1#context" 1 0 --json (inputs written from tests/fixtures/webSourceLocation.ts) exits 0 with one match and:
      "runtime": { "id": "bun", "version": "1.4.2", "node_compatibility_version": "26.3.0", "v8_heap_limit_bytes": null, "process_memory_limit_bytes": 738197504 }
      The last limitation ends: "The codec ran on Bun, which does not enforce REA's V8 heap flags; a Windows Job Object limited its committed memory to 738197504 bytes, which is not a heap or RSS limit."
    • bun scripts\rea.mjs analyze-javascript-application <dir> --format json on a directory with the 1,732,994-byte @vue/compiler-sfc browser bundle (src/big.js) and a one-line module (src/small.js): exit 0 in about 5 seconds. src/big.js reports resource_constraint / memory, "reached its 1610612736-byte process memory limit while analyzing src/big.js during semantic", with "reported_limits":{"worker_heap_limit_bytes":null,"worker_process_memory_limit_bytes":1610612736,"worker_timeout_ms":300000}. src/small.js and its small export are still recovered by static analysis.
    • The same command with --max-heap-mb 128 on a two-file app whose main.js has 4000 imports reaches the parent result-budget path and exits 0 with partial coverage. The target.js failure reports "reported_limits":{"worker_heap_limit_bytes":null,"worker_process_memory_limit_bytes":671088640,"worker_timeout_ms":300000,"semantic_result_budget_bytes":0}; coverage limits are javascript_analysis_semantic_result_budget_bytes, javascript_analysis_worker_process_memory_limit_bytes, javascript_analysis_worker_timeout_ms and javascript_analysis_parent_result_budget_bytes, with no worker heap limit.
  • Remaining proof gaps:

    • Node.js branches were not run locally (no Node.js on this machine); CI covers them.
    • javascriptSourceAnalysisWorker.test.ts cannot run under Vitest on Bun: every test in the file, including the three existing ones on base, fails with [module runner] "import.meta.resolve" is not supported. Its Bun-side behavior is covered by the analyzer CLI runs above; CI runs the file under Node.js.
    • The codec cannot reach its real 704 MiB limit within its own input budgets, so its reached-limit path is proven only with a smaller applied limit and with an injected notification.
    • The 512 MiB reserve was measured on one 32-logical-processor host; Bun's baseline commit grows with thread count.

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

Local validation ran under Bun 1.4.2 on Windows x64 without Node.js installed. npm-based wrapper scripts cannot run here, so the underlying scripts were run with bun --bun. The native artifact built by this commit's CI was installed in the ignored native/windows/build; base runs used the artifact from the published rea-agents@6.4.0 package, because base's loader rejects the new jobMemoryLimit inspection key.

Command Result
bun --bun run compile; artifacts:skills, artifacts:mcp-catalog, artifacts:product-catalog, artifacts:managed-evidence, artifacts:error-schema Pass
bun --bun run typecheck Pass
bun --bun run lint Pass, 0 errors. No new warnings compared with base. Already-flagged functions grew slightly: CaptureFailures mapping (complexity 59→63), HistoricalCaptureDecoder#inspect (30→31), JavaScriptAnalysisWorker#analyze (47→48; file 710→756 lines), verify-windows-native.mjs (675→680 lines).
oxfmt --check on touched files Pass, on LF-normalized copies (the Windows checkout uses CRLF, which oxfmt flags in every file).
bun --bun run docs:check:built, bun --bun run mcp-catalog:check:built Pass
bun --bun run test:local -- on JavaScriptWorkerMemoryLimit, WindowsOwnedProcess, WindowsNativeLoader, ProviderProcessOutput, CaptureFailures, WebSourceLocationService, runtimeVersion, Doctor, GhidraDoctor 118 of 120 pass. The 2 failures are GhidraDoctor Windows-authority tests that fail identically on base.
bun scripts/verify-windows-native.mjs . (full native conformance, under Bun instead of Node.js) ok: true, no false control. processes.jobMemoryLimit: the 128 MiB-limited child exited 1 at a peak of 145,399,808 bytes with the limit reached; the control exited 0 at 56,586,240 bytes; invalid limits, unlimited poll shape, wrapper reporting and missing-capability refusal all true.
bun --bun node_modules/vitest/vitest.mjs run on src/application src/domain src/browser/history src/javascript src/ghidra src/process src/windows tests/boundary/javascript tests/boundary/process, the webSourceLocationMcp, javascriptAnalysisResources, outputSchemaSemanticConstraints and compactInputSchemaMcp MCP boundary tests, targetDiagnostics, and the webSourceLocationCli and historicalCaptureResources acceptance tests, on this branch and on base Base: 2986 passed, 113 failed. This branch: 3013 passed, 114 failed (28 more tests). Fixed by this change: the real-codec sourceMapDecoder test, webSourceLocationCli, and the HAR exhaustion acceptance test. Failing only on this branch: the 4 new tests in javascriptSourceAnalysisWorker.test.ts, which fail like the file's existing tests because Vitest's module runner on Bun does not support import.meta.resolve. Every other failure is the same test on both and environment-specific under Bun on Windows (e.g. that import.meta.resolve limitation, POSIX-mode process tests, Linux/macOS Hopper installers, Ghidra Windows authority).
Built-CLI smoke runs: doctor --json, trace-web-source-location, analyze-javascript-application (twice) Output shown above.

CI on this commit (fork push run, Node.js on GitHub-hosted runners):

  • Build bundled Windows x64 native controls (MinGW build and artifact verification): passed. Its rea-windows-x64.node SHA-256 is 5fa70a01…11aac, byte-identical to the artifact used in the local runs above.
  • Windows capability and package lane (npm run verify:windows-native on windows-latest, including the jobMemoryLimit controls): passed.
  • Static checks, Build package, Docs Generation, all four test shards, Test (coverage + timing), all four native package baselines, the Windows NativeAOT fixture, and the real browser, web runtime, web source map, web network, JavaScript recovery, EVM, binary layout, recorded crash and Apple artifact lanes: passed (29 jobs).
  • Real Apple artifact CLI/MCP E2E (native-regressions) failed in "Run native Apple and process boundary regressions", which also fails the CI required gate. The same step failed on upstream main at this PR's base 966dc3a (morluto/rea run 38116233537), and upstream has since changed its macOS lanes (3f4ea32, "ci: reduce macOS runner contention"). On macOS this change only adds the memoryLimitReached pass-through and the refusal of a requested limit, which the policy never requests outside Windows.

Compatibility, safety, and release

  • Breaking changes or migration steps: none for Node.js; the only Node.js-visible change is the new details object on the doctor node check. Consumers validating trace_web_source_location against the old schema could not have received Bun output before (it always failed). Bun-on-Windows users need the matching package artifact; an older artifact refuses the helper launch instead of running it unbounded.
  • Real Hopper/Ghidra, browser, or OS coverage: Windows x64 under Bun locally; native code built and verified by CI's Windows lanes. No browser, Hopper or Ghidra workflows involved.
  • Package or release metadata impact: rebuilt native/windows/build artifact.
  • Security, privacy, process, or containment review: the completion port is private and not inheritable, the handle list still restricts inheritance, and closing a limited job also closes its port. Job membership, kill-on-close and breakaway checks are unchanged. No new file access.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

…n Windows

Under Bun, REA recorded Node/V8 identity it did not observe and its JavaScript helper processes ran without a memory bound, because Bun ignores the V8 heap flags REA passes. Source-map trace Evidence claimed runtime node-v8 with Bun's synthetic V8 heap limit (the decoder then rejected that limit, so trace_web_source_location always failed), analysis resource failures reported heap limits Bun does not enforce, the HAR decoder claimed a fixed 192 MiB heap, and doctor reported Bun's Node.js compatibility version as the Node version.

Identify the runtime once from process.versions. Source-map reports record id bun with the Bun and Node.js compatibility versions and a null v8_heap_limit_bytes; the decoder checks the heap budget only for V8 reports and rejects a report from a runtime other than the launching one. The analysis worker reports a null heap limit under Bun, HAR limitations and failures omit heap sizes under Bun, and the doctor node check reports Bun and passes when its Node.js compatibility version is supported.

On Windows under Bun, each helper's owned Job Object now carries a commit limit of its heap budget plus a measured 512 MiB Bun runtime reserve (704 MiB for the codec and HAR decoder, 1536 MiB for the default analyzer). The native addon accepts an optional page-aligned job memory limit on process_spawn, verifies it by readback, associates a private completion port before the child is created, and reports a sticky memoryLimitReached and peak job commit on each poll; inspect reports jobMemoryLimit. A requested limit with an artifact lacking the control refuses the launch, and non-Windows launchers refuse a limit.

Reaching the limit is classified by the job notification rather than Bun's exit status: the analyzer fails the current source with a memory resource failure reporting worker_process_memory_limit_bytes, the codec returns a memory resource failure, and the HAR decoder returns its memory failure with process_memory_limit_bytes. Bun source-map Evidence records process_memory_limit_bytes, verified against the launcher. Node.js launches and outputs are unchanged.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T11:33:04.914477Z 169ca86 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24801218e2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread native/windows/src/process.cc
@morluto

morluto commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Bun enjoyers unite!

@morluto
morluto merged commit 66c2157 into morluto:main Oct 11, 2026
20 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 169ca86b35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// A decoder that survived a failed allocation may still reply; its
// reply is not trusted as complete once the limit was reached.
if (completed.memoryLimitReached === true)
throw captureMemoryFailure(input, CAPTURE_PROCESS_MEMORY_LIMIT_REASON);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the completed decoder snapshot in memory failures

When a limited HAR decoder catches an allocation failure and exits 0 (the scenario this branch explicitly handles), runOwnedCommand has already returned its stdout/stderr and exit-status snapshot, but this throws a fresh error without passing completed; captureMemoryFailure therefore emits no captured_output or observed exit metadata, unlike the nonzero-exit path. Carry the snapshot into the memory failure through the existing redaction path so these local diagnostics are not discarded.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants