{"record":{"id":"77f10a0d9b00ac6c","repo":"abhigyanpatwari/GitNexus","slug":"analyzer-build-symbolic-links-are-not-supported-m","errorCode":null,"errorMessage":"Analyzer build symbolic links are not supported; materialize the build tree: ${entry.absolutePath}","messagePattern":"Analyzer build symbolic links are not supported; materialize the build tree: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":847,"sourceCode":"      SHA256_PATTERN.test(cached.digest) &&\n      isDeepStrictEqual(cached.state, entry.state)\n    ) {\n      digest = cached.digest;\n    } else if (entry.kind === 'file') {\n      const stable = hashStableFile(entry.absolutePath);\n      entry.state = stable.state.link;\n      digest = stable.digest;\n      options.onHashedInput?.({\n        kind: 'build',\n        path: entry.absolutePath,\n        bytes: stable.bytes,\n      });\n    } else if (entry.kind === 'symlink') {\n      // Imported files and directories are resolved through symlinks by Node.\n      // Hashing only link text would let bytes outside buildRoot change without\n      // changing the receipt. Reject them instead of inventing an incomplete\n      // recursive trust boundary (target containment, cycles, and TOCTOU).\n      throw new Error(\n        `Analyzer build symbolic links are not supported; materialize the build tree: ${entry.absolutePath}`,\n      );\n    }\n\n    updateCanonicalFrame(hash, [\n      'build-entry',\n      entry.relativePath,\n      entry.kind,\n      digest ? digestBytes(digest) : Buffer.alloc(0),\n    ]);\n    nextEntries.push({\n      relativePath: entry.relativePath,\n      kind: entry.kind,\n      state: entry.state,\n      ...(digest ? { digest } : {}),\n    });\n  }\n","sourceCodeStart":829,"sourceCodeEnd":865,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/analyzer-identity.ts#L829-L865","documentation":"The build-tree receipt hashes real file content, but Node resolves imports through symlinks, so a symlink's target bytes outside buildRoot could change analyzer behaviour without changing anything the receipt covered (target containment, cycles, TOCTOU are all unsolved at that boundary). Rather than invent an incomplete trust model, the scan rejects any symbolic link inside the build tree and asks for a materialized (fully copied) tree.","triggerScenarios":"Any entry classified as a symlink during collectBuildEntries/hashing — typically a pnpm-style or workspace install where parts of dist/ are links into a shared store, a developer manually symlinking a subdirectory (dist/vendor -> ../../shared), or a monorepo linking gitnexus's dist to a central location while running analyze against the link path.","commonSituations":"pnpm workspaces or custom linkers exposing the analyzer build through symlinks; developers symlinking shared grammars or vendored modules into dist; Docker volume or deploy tooling replacing directories with links.","solutions":["Replace the link with a real copy: 'cp -Lr <link> <real-dir> && rm <link>' (materialize the tree).","Configure the package manager to hoist/materialize instead of linking the gitnexus install (e.g. pnpm node-linker=hoisted for this dependency).","Run gitnexus from a plain npm-style install where dist/ is real files.","Change build tooling so post-build symlinking of dist subdirectories never runs in environments that execute analyze."],"exampleFix":"# before: dist/vendor-grammars -> ../../shared-grammars (symlink)\n$ ls -l dist/vendor-grammars\nlrwxrwxrwx dist/vendor-grammars -> ../../shared-grammars\n\n# after: materialize the tree\n$ rm dist/vendor-grammars && cp -r ../../shared-grammars dist/vendor-grammars","handlingStrategy":"validation","validationCode":"import { lstatSync, readdirSync } from 'node:fs';\n\nfunction buildTreeHasNoSymlinks(buildRoot: string): boolean {\n  const stack = [buildRoot];\n  while (stack.length > 0) {\n    const dir = stack.pop()!;\n    for (const entry of readdirSync(dir, { withFileTypes: true })) {\n      if (entry.isSymbolicLink()) return false;\n      if (entry.isDirectory()) stack.push(`${dir}/${entry.name}`);\n    }\n  }\n  return true;\n}","typeGuard":"function isBuildSymlinkError(error: unknown): boolean {\n  return error instanceof Error && /^Analyzer build symbolic links are not supported/.test(error.message);\n}","tryCatchPattern":"try {\n  identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n} catch (error) {\n  if (isBuildSymlinkError(error)) {\n    reportUserError('Materialize the build tree: replace links inside dist with real copies (cp -Lr).');\n  }\n  throw error;\n}","preventionTips":["Use a hoisted/materialized install for the environment running gitnexus (e.g. pnpm node-linker=hoisted).","Never symlink subdirectories into dist in deploy scripts.","Verify with find dist -type l before running analyze on custom builds."],"tags":["gitnexus","analyzer-identity","symlink","build-tree","pnpm"],"backgroundTag":"symlink-not-supported","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}