abhigyanpatwari/GitNexus · error
Analyzer dependency graph exceeded ${limits.runtimeEdges} ed
Error message
Analyzer dependency graph exceeded ${limits.runtimeEdges} edges: ${packageRoot} What it means
Thrown by collectRuntimePackages when the cumulative edge count of the dependency graph traversal exceeds limits.runtimeEdges (default 100_000). Each declared dependency of each queued package increments budget.edges once; the throw is a hard ceiling that prevents a pathological or cyclic dependency closure from running unbounded work during analyzer-identity resolution.
Source
Thrown at gitnexus/src/core/analyzer-identity.ts:1235
const parent = queue[index];
// The declared half is enumerated for every package; the resolved-location
// half is scoped to the root package, where the 1998-resolution /
// 8829-extra-guard blow-up documented on
// `undeclaredLocalDevDependencyNames` cannot occur. Dropping this scope is
// the expensive regression, so it is pinned by a guard-count test.
const dependencies =
parent.root === packageRoot
? [
...new Set([
...dependencyNames(parent.manifest),
...undeclaredLocalDevDependencyNames(parent, pathGuards, limits),
]),
].sort(compareBytes)
: dependencyNames(parent.manifest);
for (const dependencyName of dependencies) {
budget.edges += 1;
if (budget.edges > limits.runtimeEdges) {
throw new Error(
`Analyzer dependency graph exceeded ${limits.runtimeEdges} edges: ${packageRoot}`,
);
}
const childRoot = resolveDependencyPackageRoot(
parent.root,
dependencyName,
pathGuards,
limits,
);
if (!childRoot) {
edges.push({
parentLocator: parent.locator,
parentLabel: parent.label,
dependencyName,
childLocator: '<missing>',
childLabel: '<missing>',
});
continue;View on GitHub (pinned to d540b00184)
Solutions
- Run `npm ls --all` / `pnpm why` in the package root to find the heaviest dependency trees and prune unused devDependencies that bloat the closure.
- Shrink the install: remove bundled optionalDependencies or use `npm install --omit=optional` / `--production` for the environment running the analyzer.
- If the closure is legitimately large, lower work by hoisting with a single node_modules root and removing duplicate workspace links.
- As a last resort on a constrained host, pass options.traversalLimits.runtimeEdges (it is clamped to the default ceiling, never above) only to FAIL FASTER in tests — it cannot raise the production bound.
Example fix
// before: every workspace package carries its own node_modules copy // -> edge count explodes across duplicate closure walks // // after: hoist to a single root install // $ rm -rf **/node_modules && npm install --workspaces --include-workspace-root // (root package.json "workspaces" field drives a single hoisted node_modules)
Defensive patterns
Strategy: validation
Validate before calling
// Before resolving identity, sanity-check the dependency closure size.
const { execSync } = require('node:child_process');
const path = require('node:path');
function assertEdgeBudgetFeasible(packageRoot) {
// Approximate edge count: sum of declared deps across the unique closure.
// `npm ls --all` prints one line per (package,parent) edge.
let out;
try { out = execSync('npm ls --all --parseable', { cwd: packageRoot, stdio: ['ignore','pipe','ignore'], maxBuffer: 64*1024*1024 }).toString(); }
catch (e) { out = e.stdout?.toString() ?? ''; }
const edgeEstimate = out.split('\n').filter(Boolean).length;
if (edgeEstimate > 90_000) {
throw new Error(`Dependency edge estimate ${edgeEstimate} is near the 100000 analyzer limit; prune or dedupe first.`);
}
}
// assertEdgeBudgetFeasible(process.cwd()); Prevention
- Keep dependency closures lean: run `npm ls --all` periodically and prune unused devDependencies.
- Hoist dependencies to a single node_modules in workspaces to avoid duplicate closure walks.
- Run the analyzer against a production install (--omit=dev) when possible to shrink the closure.
- Note options.traversalLimits.runtimeEdges can only LOWER the cap (clamped via Math.min), never raise it — the 100k ceiling is a hard production bound.
When it happens
Trigger: resolveAnalyzerRunnerIdentity() cold path (cache miss) -> collectDependencyInputs -> collectRuntimePackages, where the root package's dependency closure (declared deps plus undeclared-but-resolved local devDeps) is walked with a worklist. The counter ticks once per parent->dependencyName pair across every package; if it crosses 100k the loop aborts on the next iteration.
Common situations: A monorepo with thousands of transitive dependencies (e.g. a workspace hoisting many shared packages), a corrupted/oversized package-lock producing duplicate edges, or a development install that pulled in an enormous optional dependency tree (e.g. Playwright/Electron browsers). Also seen when node_modules is symlinked across many workspace siblings so the same dependency appears under many parents.
Related errors
- Analyzer dependency graph exceeded ${limits.runtimePackages}
- Analyzer runtime payload scan exceeded ${limits.runtimeEntri
- Analyzer runtime payload scan exceeded depth ${limits.runtim
- Analyzer runtime payload scan exceeded ${limits.runtimePaylo
- Analyzer runtime scan exceeded ${limits.runtimeBytes} bytes:
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/0308b2bf9c92fe9a.
Report an issue: GitHub.