Skip to content

fix(runtime): enforce Node heap limits - #145

Merged
steipete merged 11 commits into
mainfrom
claude/fix-cli-heap-limit
Oct 8, 2026
Merged

steipete merged 11 commits into
mainfrom
claude/fix-cli-heap-limit

Conversation

@steipete

@steipete steipete commented Oct 8, 2026 •

Copy link
Copy Markdown

What changes

bun --max-old-space-size=32 previously completed a workload retaining 256 MiB. This connects the CLI and NODE_OPTIONS flag to the fork’s per-VM managed-heap limits. CLI values override the environment; zero restores defaults. Workers inherit the process limit, retain ERR_WORKER_OUT_OF_MEMORY and exit 1 on exhaustion, and main-VM exhaustion remains fatal. ArrayBuffer backing stores are excluded. process.allowedNodeEnvironmentFlags advertises the flag.

Related upstream work: oven-sh/bun#34917 and oven-sh/bun#34924. The branch is based on bf099fbbc4a, including the resolver/Mordant, TLS and subprocess fixes. The WebKit dependency is unchanged.

CI repairs

Linux’s piped systemd-coredump collector ignored a zero core-size limit. A controlled abort with both core limits zero streamed at least 2 GiB; clearing the child’s coredump_filter reduced it to 49 KiB with the same SIGABRT. The test helper applies both settings only to intentionally fatal children and their descendants. Original heap budgets, signals and assertions are preserved. Linux CI subsequently completed with about 79 GB free and 414 KB in its core store. Its FUSE prerequisite now matches the test images.

The stdio repair adapts oven-sh/bun#38008 to borrowed fd 1/2. Isolation swaps drain pending writes before replacing the global. The VM-teardown finding is corrected in 4af0caf4d49: only Disposed uses graceful flushing; VmTeardown uses native close to release its poll and keep-alive before destroying the VM. An actual-sink backpressure regression fails on the instrumented pre-fix branch and passes on Linux and macOS, asserting native cleanup and a clean exit. The existing 8 MiB swap regression preserves every byte.

The remaining test repairs preserve their contracts: the CJS snapshot includes the existing module.exports marker; compile-cache exit cases share an immutable 2,000-module input corpus while keeping fresh output caches and all original assertions and deadlines. Sample-dependent CPU-profile fixtures require actual CPU work alongside their existing wall-time minimum. The PTY fixture gates child exec on its initial foreground assignment and waits for the script’s foreground handoff before Ctrl-Z. A forced late-parent control reproduced its former invalid foreground state and false stop/resume failure. Concurrency, sampling intervals, heap limits and test deadlines are unchanged.

Validation

Head: 20ae7f644276950d95090bb2958e54c2afbe00b9. READY for coordinator review and merge.

  • Exact-head Linux/macOS native CI, Rust CI, and Windows x64/ARM64 validation passed. Windows was tagless with publish=false.
  • Linux affected proof: 828 passed, six existing skips; the additional profiler file passed all 16 tests. macOS affected/profiler proof: 843 passed, six existing skips and one todo across nine files. The PTY file passed 15 applicable macOS tests, and the final focused case passed all seven assertions. All 12 Rust cross-check targets passed.
  • The runtime correction and both final fixture corrections have scoped-clean P2 reviews. The final two commits change only tests; runtime sources match 4af0caf4d49.
  • OpenClaw agents-support at b3196dfda33555857eae9cd52ea29367a91bd9ea: 5,193 passed, 30 skipped, three pending, zero failures across its 390-file selection, using the exact runtime build with NODE_OPTIONS=--max-old-space-size=8192 and private HOME/state/temp roots.

An earlier Linux unit-fast run retained one provider-catalog retention failure, also seen on a byte-identical test before this PR on Bun main b3684189fe1f. Its cause remains unresolved; no retention fix or passing retry is claimed. The successful agents-support run supplies the requested consumer check.

The PR remains open and unmerged. No release, shared pin or owner admission has changed.

@steipete
steipete force-pushed the claude/fix-cli-heap-limit branch from b02d43a to 3365426 Compare October 8, 2026 14:21
Adapts oven-sh#38008 to borrowed process descriptors and waits for pending writes before the next isolated file can register its stdio polls. Covers serial, parallel, and backpressured output without closing fd 1 or 2.

Install the existing python3-fuse test prerequisite in native Linux CI. macOS: 395 passed across isolation, process and FileSink suites; small-output Linux regression and FUSE cases passed; backpressure negative control loses output on the prior implementation. P2 scoped-clean.
@steipete

steipete commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

Readiness remains held at 20ae7f644276950d95090bb2958e54c2afbe00b9, despite its green native Linux, macOS, and Windows runs.

The earlier VmTeardown ownership finding is corrected and independently reviewed. The latest TTY fixture has a separate assertion gap: Perl POSIX::tcsetpgrp returns undef on failure, and == 0 can accept that value through numeric coercion. Bun can resume the script without restoring its foreground ownership, so RESUMED alone does not prove the simulated fg handoff.

A narrow test-only correction is prepared: reject an undefined tcsetpgrp result and verify tcgetpgrp equals the intended group before both initial release and resume. Actual Perl negative controls reproduce the old false acceptance and reject it after the correction; a real PTY control verifies both valid handoffs. Independent P2 review is scoped-clean. Existing end-to-end assertions, deadlines, and skips are unchanged.

The complete native test file has not yet run with that correction. It must pass through the repository's CI runner, followed by exact-head CI after publication, before this hold is cleared. No fork merge or release is requested by this update.

Prepare the 2000-module, 64-function input corpus once for the exit-mode group. Each mode still gets a fresh empty compile-cache directory, its original 30-second case limit, all integrity and warm-read checks, and the existing strict signal-exit assertions.

The Windows two-case matrix retains 16028 assertions and drops from 34-37 seconds to 25 seconds by avoiding duplicate fixture work. The full Windows module suite passes 205 tests with existing skips/todo; P2 review is scoped-clean.
Restrict graceful stdio flushing to isolation disposal, which has an I/O drain barrier. VM teardown now uses the native close path to release pending polls and keep-alive references before destroying the VM.

Exercise real VM destruction with backpressured stdout and stderr. Internal observers assert native cleanup before the child exits, while the existing file-swap regression preserves all buffered output.
Keep the 100 ms wall-clock minimum and additionally require 100 ms of process CPU time for sample-dependent workloads. A descheduled process can otherwise consume the entire fixture window without executing the profiled work.

Preserve every profile assertion, sampling interval, concurrency setting, and test deadline.
Keep the outer shell from reassigning the terminal after Bun has already handed it to the script. A pipe gates child exec on the initial foreground assignment, and READY waits for the script to become foreground.

Read complete marker lines before parsing process groups. Preserve the stop/continue assertions, concurrency, and existing hang guards.
@steipete

steipete commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

Replying to the teardown finding: confirmed and fixed in 4af0caf4d49.

The entire graceful end(None) branch now requires ContextStopped(Disposed). VmTeardown takes the existing native close path, releasing the poll and keep-alive reference before VM destruction. The isolation-swap drain barrier is preserved.

The regression uses the actual process stdout/stderr sink and leaves the parent's data pipe unread until exit. It verifies native EAGAIN, pending data, a poll and a keep-alive before exit. An observer in the real VM stop sweep checks VmTeardown, no retained poll or keep-alive, and no pending isolation flush before emitting its success marker; the parent requires exit 0 without a signal. The instrumented pre-fix control fails for both descriptors, with stdout reporting stdio poll retained at VM teardown. Both regressions and the existing 8 MiB file-swap flush test pass on Linux and macOS.

Linux affected proof passed 828 tests with six existing skips. macOS passed 843 tests across the affected suites plus the profiler file, with six existing skips and one todo. All 12 Rust cross-check targets passed, and the correction has a scoped-clean P2 review. The exact runtime build also passed OpenClaw agents-support: 5,193 passed, 30 skipped, three pending, zero failures with NODE_OPTIONS=--max-old-space-size=8192.

Current head is 20ae7f644276950d95090bb2958e54c2afbe00b9. Its two later commits only correct CPU-profile workload readiness and a PTY fixture foreground-handoff race exposed by CI; runtime sources are unchanged. Their assertions, concurrency, and deadlines are preserved, and both changes have scoped-clean P2 reviews. Final exact-head native Linux/macOS CI, Rust CI, and Windows x64/ARM64 validation are green. Native and Windows compatibility logs show no fallback rerun. All 22 checks are completed with success or expected skips; publication was disabled. READY for coordinator review and merge at 20ae7f644276950d95090bb2958e54c2afbe00b9. The PR remains open and unmerged.

@steipete
steipete merged commit 2428762 into main Oct 8, 2026
22 checks passed
@steipete
steipete deleted the claude/fix-cli-heap-limit branch October 8, 2026 20:01
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.

1 participant