{"record":{"id":"32e663edc50898e4","repo":"google/zx","slug":"can-t-link-node-modules-alias-already-exists","errorCode":null,"errorMessage":"Can't link node_modules: ${alias} already exists","messagePattern":"Can't link node_modules: (.+?) already exists","errorType":"exception","errorClass":"Fail","httpStatus":null,"severity":"error","filePath":"src/cli.ts","lineNumber":191,"sourceCode":"  }\n}\n\nfunction linkNodeModules(cwd: string, external: string): string {\n  const nm = 'node_modules'\n  const alias = path.resolve(cwd, nm)\n  const target =\n    path.basename(external) === nm\n      ? path.resolve(external)\n      : path.resolve(external, nm)\n  const aliasStat = lstat(alias)\n  const targetStat = lstat(target)\n\n  if (!targetStat?.isDirectory())\n    throw new Fail(\n      `Can't link node_modules: ${target} doesn't exist or is not a directory`\n    )\n  if (aliasStat?.isDirectory() && alias !== target)\n    throw new Fail(`Can't link node_modules: ${alias} already exists`)\n  if (aliasStat) return ''\n\n  fs.symlinkSync(target, alias, 'junction')\n  return alias\n}\n\nfunction lstat(p: string) {\n  try {\n    return fs.lstatSync(p)\n  } catch {}\n}\n\nasync function readScript() {\n  const [firstArg] = argv._\n  let script = ''\n  let scriptPath = ''\n  let tempPath = ''\n  let argSlice = 1","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/google/zx/blob/00a2c484e219c2e84bfc3a199febf7fbce2cfbf4/src/cli.ts#L173-L209","documentation":"Thrown by linkNodeModules() when the script's directory (cwd) already contains a real node_modules directory that differs from the requested symlink target. zx refuses to overwrite or destroy an existing real node_modules folder; it will only replace a missing entry or an existing symlink that points at the same target.","triggerScenarios":"Running a script with `--prefer-local=<external>` from a directory that already has its own `node_modules` real directory whose absolute path is not equal to the external target. Switching prefer-local targets without first removing the previously created link/dir also lands here.","commonSituations":"Running zx from inside a project root (which already ran npm install) with -l pointing elsewhere; monorepo where the script dir and the deps dir differ; stale node_modules left over from an earlier prefer-local run.","solutions":["Remove the existing node_modules in the script's directory: `rm -rf node_modules` (or `unlink node_modules` if it is a symlink).","Run zx from a clean or temporary directory that has no node_modules.","Drop the --prefer-local flag so zx does not attempt linking.","Point --prefer-local at the same absolute path as the existing node_modules if you want the no-op path (alias === target returns '')."],"exampleFix":"// before: run.mjs sits in a dir that already has node_modules\n$ zx --prefer-local=../ext run.mjs\n// after: clear the conflicting dir first\n$ rm -rf node_modules && zx --prefer-local=../ext run.mjs","handlingStrategy":"validation","validationCode":"import fs from 'node:fs'\nimport path from 'node:path'\n\nfunction isSafeToLink(cwd: string, target: string): boolean {\n  const alias = path.resolve(cwd, 'node_modules')\n  if (alias === target) return true            // no-op link\n  const stat = fs.lstatSync(alias)\n  return !stat.isDirectory() || stat.isSymbolicLink()  // real dir blocks linking\n}\n\nif (!isSafeToLink(scriptDir, target)) {\n  throw new Error(`remove existing node_modules in ${scriptDir} before --prefer-local`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runScript()\n} catch (e) {\n  if (e instanceof Fail && /already exists/.test(e.message)) {\n    console.error('clean node_modules in the script dir first')\n  }\n  throw e\n}","preventionTips":["Run zx from a clean/temp directory when using --prefer-local.","Remove or unlink cwd/node_modules before switching prefer-local targets.","Never assume zx will overwrite an existing real node_modules — it deliberately will not."],"tags":["node-modules","prefer-local","filesystem","symlink","cli"],"backgroundTag":null,"analyzedSha":"00a2c484e219c2e84bfc3a199febf7fbce2cfbf4","analyzedAt":"2026-08-13T02:11:06.305Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}