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

  1. Run `npm ls --all` / `pnpm why` in the package root to find the heaviest dependency trees and prune unused devDependencies that bloat the closure.
  2. Shrink the install: remove bundled optionalDependencies or use `npm install --omit=optional` / `--production` for the environment running the analyzer.
  3. If the closure is legitimately large, lower work by hoisting with a single node_modules root and removing duplicate workspace links.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/0308b2bf9c92fe9a. Report an issue: GitHub.