{"record":{"id":"f8efe585484d94c8","repo":"abhigyanpatwari/GitNexus","slug":"analyzer-package-lock-symbolic-link-does-not-resol","errorCode":null,"errorMessage":"Analyzer package lock symbolic link does not resolve to a file: ${candidate}","messagePattern":"Analyzer package lock symbolic link does not resolve to a file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":965,"sourceCode":"    ancestors += 1;\n    if (ancestors > limits.resolutionAncestors) {\n      throw new Error(\n        `Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}`,\n      );\n    }\n    const candidate = path.join(cursor, 'package-lock.json');\n    recordDependencyPathGuard(pathGuards, candidate);\n    // Preserve the link path so ReadableFileState guards both the link and its\n    // resolved target. Realpathing here would miss a later retarget while the\n    // old target remained unchanged.\n    try {\n      const link = lstatSync(candidate);\n      if (link.isFile()) return path.resolve(candidate);\n      if (link.isSymbolicLink()) {\n        try {\n          const target = statSync(candidate);\n          if (!target.isFile()) {\n            throw new Error(\n              `Analyzer package lock symbolic link does not resolve to a file: ${candidate}`,\n            );\n          }\n        } catch {\n          throw new Error(\n            `Analyzer package lock symbolic link does not resolve to a file: ${candidate}`,\n          );\n        }\n        return path.resolve(candidate);\n      }\n      throw new Error(`Analyzer package lock is not a regular file: ${candidate}`);\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n    }\n    const parent = path.dirname(cursor);\n    if (parent === cursor) return null;\n    cursor = parent;\n  }","sourceCodeStart":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/analyzer-identity.ts#L947-L983","documentation":"When a candidate package-lock.json is a symbolic link, findNearestPackageLock stats the target and requires it to be a regular file; if the target is a directory (or otherwise not a file) this inner branch throws. A lockfile link pointing at a directory cannot be hashed as a lockfile, and silently skipping it would drop dependency-freeze state from the receipt, so the condition is fatal rather than ignored.","triggerScenarios":"package-lock.json symlinked to a directory — e.g. a dotfiles/config manager that links 'package-lock.json -> ../shared/' by mistake, or a setup script that linked the lockfile to a folder containing lockfiles instead of to one lockfile file. Hit during any resolveAnalyzerRunnerIdentity call that walks ancestors from the package root.","commonSituations":"Shared-lockfile monorepo setups done with symlinks; Nix/conda-style environments where lockfiles are links into a store and the store path is a directory; typo'd 'ln -s' targets.","solutions":["Point the link at an actual lockfile file: 'ln -sfn /path/to/shared/package-lock.json package-lock.json'.","Or replace the link with a real copy of the lockfile.","Remove stray directory links named package-lock.json from ancestor directories of the gitnexus install."],"exampleFix":"# before: link points at a directory\n$ ln -sfn ../shared-locks package-lock.json\n\n# after: link points at the lockfile itself\n$ ln -sfn ../shared-locks/package-lock.json package-lock.json","handlingStrategy":"validation","validationCode":"import { lstatSync, statSync } from 'node:fs';\n\nfunction lockfileLinksResolveToFiles(packageRoot: string): boolean {\n  let cursor = packageRoot;\n  while (true) {\n    const candidate = `${cursor}/package-lock.json`;\n    try {\n      const link = lstatSync(candidate);\n      if (link.isFile()) return true;\n      if (link.isSymbolicLink()) return statSync(candidate).isFile();\n      return false;\n    } catch {\n      /* ENOENT: keep walking ancestors */\n    }\n    const parent = path.dirname(cursor);\n    if (parent === cursor) return true;\n    cursor = parent;\n  }\n}","typeGuard":"function isLockfileSymlinkTargetError(error: unknown): boolean {\n  return error instanceof Error && /^Analyzer package lock symbolic link does not resolve to a file:/.test(error.message);\n}","tryCatchPattern":"try {\n  identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n} catch (error) {\n  if (isLockfileSymlinkTargetError(error)) {\n    reportUserError(`Lockfile link points at a directory: ${error.message}; point it at the lockfile file or remove it.`);\n  }\n  throw error;\n}","preventionTips":["When sharing lockfiles via symlinks, always link to the file, never its parent directory.","Test provisioning scripts with readlink + stat before shipping environments that run gitnexus."],"tags":["gitnexus","analyzer-identity","package-lock","symlink","filesystem"],"backgroundTag":"symlink-target-not-a-file","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}