abhigyanpatwari/GitNexus · error · Error

Analyzer package-lock lookup exceeded ${limits.resolutionAnc

Error message

Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}

What it means

findNearestPackageLock() walks from the package root up through ancestors looking for package-lock.json, counting one ancestor per iteration and throwing past limits.resolutionAncestors (default 256). The walk must actually visit each level (it stats and records a guard per candidate), so the count also bounds the loop against pathological or cyclic-looking path structures. 256 levels is far deeper than any real install, so hitting it almost always means a constructed layout.

Source

Thrown at gitnexus/src/core/analyzer-identity.ts:951

  candidate: string,
): DependencyPathGuardResult {
  if (guards.has(candidate)) return guards.get(candidate) ?? null;
  const result = snapshotDependencyPathGuard(candidate);
  guards.set(candidate, result);
  return result;
}

function findNearestPackageLock(
  packageRoot: string,
  pathGuards: Map<string, DependencyPathGuardResult>,
  limits: AnalyzerIdentityTraversalLimits,
): string | null {
  let cursor = packageRoot;
  let ancestors = 0;
  while (true) {
    ancestors += 1;
    if (ancestors > limits.resolutionAncestors) {
      throw new Error(
        `Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}`,
      );
    }
    const candidate = path.join(cursor, 'package-lock.json');
    recordDependencyPathGuard(pathGuards, candidate);
    // Preserve the link path so ReadableFileState guards both the link and its
    // resolved target. Realpathing here would miss a later retarget while the
    // old target remained unchanged.
    try {
      const link = lstatSync(candidate);
      if (link.isFile()) return path.resolve(candidate);
      if (link.isSymbolicLink()) {
        try {
          const target = statSync(candidate);
          if (!target.isFile()) {
            throw new Error(
              `Analyzer package lock symbolic link does not resolve to a file: ${candidate}`,
            );

View on GitHub (pinned to 52924ef12c)

Solutions

  1. Move the gitnexus install to a normal-depth location; real installs are dozens of levels deep at most.
  2. In tests, only tighten resolutionAncestors when the fixture genuinely has few ancestor levels, and keep it above the actual depth.
  3. Verify no mount/bind arrangement creates an effectively unbounded parent chain.
Defensive patterns

Strategy: try-catch

Type guard

function isLockfileAncestorLimitError(error: unknown): boolean {
  return error instanceof Error && /^Analyzer package-lock lookup exceeded \d+ ancestors:/.test(error.message);
}

Try / catch

try {
  identity = resolveAnalyzerRunnerIdentity(import.meta.url);
} catch (error) {
  if (isLockfileAncestorLimitError(error)) {
    reportUserError('Install path is pathologically deep; move gitnexus to a normal-depth directory.');
  }
  throw error;
}

Prevention

When it happens

Trigger: The GitNexus package root sits more than 256 directory levels below the filesystem root, or a test set resolutionAncestors to a tiny value via traversalLimits. Because the loop increments before checking, even the first level counts, so a limit of 1 throws on any non-root start.

Common situations: Tests tightening resolutionAncestors for speed; pathologically nested temp/fixture directories (build tools that mirror deep scoped package names); a filesystem loop presented through mounts.

Related errors


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