abhigyanpatwari/GitNexus · error

Analyze stopped before running out of memory: ${Math.round(h

Error message

Analyze stopped before running out of memory: ${Math.round(heapUsedNow / 1024 / 1024)}MB of the ${Math.round(heapLimitNow / 1024 / 1024)}MB Node heap in use at parse chunk ${chunkIdx + 1}/${numChunks} (#2649). ${heapPressureRemedy(heapLimitNow)}

What it means

Mid-loop heap guard in parse-impl.ts (#2649): between parse chunks, if heapUsed exceeds 0.92 of V8's heap_size_limit (shouldAbortForHeapPressure), analyze aborts with heap numbers and a remedy instead of entering V8's ineffective-mark-compact death spiral (2s+ GC pauses that also falsely idle-timeout healthy workers). The remedy (heapPressureRemedy) branches: if the current heap limit sits well below what the RAM-aware auto-sizer would grant (an inherited --max-old-space-size / NODE_OPTIONS pin), drop the pin — GitNexus sizes itself; otherwise the machine is the ceiling and only .gitnexusignore exclusions or bigger hardware help. GITNEXUS_MEMORY=off disables the abort (proceed-at-own-risk).

Source

Thrown at gitnexus/src/core/ingestion/pipeline-phases/parse-impl.ts:1272

        handleWorkerStartupFailure(err);
      }
      pendingRound = { entries: started.entries, missResults };
    };

    for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
      if (heapProbeEveryN > 0 && chunkIdx > 0 && chunkIdx % heapProbeEveryN === 0) {
        logHeapProbe(
          `parse-chunk-${chunkIdx}`,
          `nodes=${graph.nodeCount} parsedFiles=${allParsedFiles.length}`,
        );
      }
      // #2649 mid-loop heap guard: fail actionably BEFORE V8 enters the
      // ineffective-mark-compact death spiral (which also falsely times out
      // healthy workers). The pool is torn down by this function's finally.
      const heapUsedNow = process.memoryUsage().heapUsed;
      const heapLimitNow = v8.getHeapStatistics().heap_size_limit;
      if (shouldAbortForHeapPressure(heapUsedNow, heapLimitNow)) {
        throw new Error(
          `Analyze stopped before running out of memory: ${Math.round(heapUsedNow / 1024 / 1024)}MB of the ` +
            `${Math.round(heapLimitNow / 1024 / 1024)}MB Node heap in use at parse chunk ${chunkIdx + 1}/${numChunks} (#2649). ` +
            heapPressureRemedy(heapLimitNow),
        );
      }
      const chunkPaths = chunks[chunkIdx];
      // Start wall-clock for the per-chunk throughput log emitted at end
      // of this iteration. The gate is computed once above; here we just
      // sample the clock if the gate is on. Computed when either
      // NODE_ENV=development OR the operator passed `--verbose`
      // (GITNEXUS_VERBOSE) — the previous `isDev`-only gate meant
      // operators running `gitnexus analyze --verbose` in production
      // never saw the log (M3 from PR #1693 review).
      const chunkStartMs: number | null = verboseThroughputLog ? Date.now() : null;

      const chunkContentPromise = chunkContentPromises[chunkIdx];
      if (!chunkContentPromise) {
        throw new Error(`Missing prefetched parse chunk ${chunkIdx + 1}/${numChunks}`);

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. If the message says the machine has more memory available: re-run without the --max-old-space-size pin (remove it from NODE_OPTIONS / node flags) — GitNexus auto-sizes its heap to the machine
  2. If the machine is at its ceiling: add .gitnexusignore entries excluding generated/vendored directories (node_modules, dist, fixtures) to shrink the graph
  3. Run analyze on a machine (or container) with more memory
  4. Last resort only: GITNEXUS_MEMORY=off declines the abort, but the process will likely die to V8 OOM or appear hung instead

Example fix

# before
$ NODE_OPTIONS=--max-old-space-size=2048 gitnexus analyze .
# after
$ unset NODE_OPTIONS
gitnexus analyze .
Defensive patterns

Strategy: validation

Validate before calling

import v8 from 'node:v8';
import os from 'node:os';

const limit = v8.getHeapStatistics().heap_size_limit;
const effectiveRam = Number(fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8')) || os.totalmem(); // honor cgroup limits
const autoCap = effectiveRam * 0.75;
if (limit < autoCap * 0.9) {
  console.warn('A --max-old-space-size / NODE_OPTIONS heap pin is below the auto-sized cap — unset it before analyze');
}
if (projectedHeapNeed > limit * 0.92) {
  console.warn('Repo likely exceeds heap — add .gitnexusignore excludes or run on a larger machine');
}

Try / catch

try {
  await runAnalyze(repoPath, options);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Analyze stopped before running out of memory')) {
    // Read the remedy in the message: drop the heap pin, or add .gitnexusignore entries, then re-run once.
    // Do NOT set GITNEXUS_MEMORY=off in automation — that trades a clean abort for a V8 OOM/hang.
  }
  throw err;
}

Prevention

When it happens

Trigger: Analyzing a very large repo with a small pinned heap (e.g. NODE_OPTIONS=--max-old-space-size=2048), inside a container whose cgroup memory limit is far below host RAM, or on a repo whose generated/vendored content inflates node counts past the heap at parse chunk N of M.

Common situations: CI images that globally export NODE_OPTIONS with a heap pin; Docker/Kubernetes memory limits; monorepos with vendored node_modules, generated protobuf/graphql code, or huge minified bundles; cgroup-limited build agents on big hosts (the auto-sizer honors the cgroup limit, not raw totalmem).

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20). Data as JSON: /api/errors/d4523abf300d1016. Report an issue: GitHub.