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
- 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
- If the machine is at its ceiling: add .gitnexusignore entries excluding generated/vendored directories (node_modules, dist, fixtures) to shrink the graph
- Run analyze on a machine (or container) with more memory
- 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
- Do not pin --max-old-space-size in CI images or NODE_OPTIONS — GitNexus sizes its heap from effective (cgroup-aware) RAM
- Keep .gitnexusignore current: exclude vendored, generated, and minified directories from indexing
- Set container memory limits with the biggest repo you index in mind; the guard honors cgroup limits, not host RAM
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
- Worker pool failed to start: ${cause}${failureDetail} The w
- C++ capture extraction exceeded its ${budgetMs}ms budget for
- ${source}: branch name is too long (max ${BRANCH_MAX_LENGTH}
- ${source} must be a boolean (true/false).
- Skipped ${skippedLarge} large files (>${maxFileSizeBytes /
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/d4523abf300d1016.
Report an issue: GitHub.