{"record":{"id":"cf896e457a816855","repo":"google/zx","slug":"can-t-link-node-modules-target-doesn-t-exist-o","errorCode":null,"errorMessage":"Can't link node_modules: ${target} doesn't exist or is not a directory","messagePattern":"Can't link node_modules: (.+?) doesn't exist or is not a directory","errorType":"exception","errorClass":"Fail","httpStatus":null,"severity":"error","filePath":"src/cli.ts","lineNumber":187,"sourceCode":"    // TODO: fix unanalyzable-dynamic-import to work correctly with jsr.io\n    await import(url.pathToFileURL(scriptPath).toString())\n  } finally {\n    rmTemp()\n  }\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._","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/google/zx/blob/00a2c484e219c2e84bfc3a199febf7fbce2cfbf4/src/cli.ts#L169-L205","documentation":"Thrown by linkNodeModules() in the zx CLI when the resolved node_modules target does not exist or is not a directory. With --prefer-local=<path>, zx symlinks cwd/node_modules to <path>/node_modules (or to <path> directly if its basename is already node_modules), so the target must be a real directory. The guard uses lstat, so a broken symlink or a file at that path also fails the isDirectory() check.","triggerScenarios":"Running `zx --prefer-local=./libs/shared run.mjs` (or `-l <path>`) where <path>/node_modules does not exist, is a file, or is a dangling symlink. Also when argv.preferLocal is a string pointing at a path whose node_modules was deleted/moved after install, or pointing --prefer-local at a file rather than a project directory.","commonSituations":"Typo in the prefer-local path; pointing at a sibling project that has never run `npm install`; CI where the external deps directory is not mounted; pointing --prefer-local at the node_modules folder of a freshly-cloned repo before deps are installed.","solutions":["Confirm the target exists and is a directory: `ls -la <prefer-local-path>/node_modules`.","Run the package manager install in the external directory so node_modules exists (e.g. `cd <path> && npm i`).","If pointing --prefer-local directly at the node_modules folder, ensure its basename is exactly `node_modules` (zx then uses it directly).","Drop the --prefer-local flag if cross-project linking is not required."],"exampleFix":"// before: ./libs/shared has no node_modules\n$ zx --prefer-local=./libs/shared run.mjs\n// after: ensure deps are installed in the external dir first\n$ (cd ./libs/shared && npm i) && zx --prefer-local=./libs/shared run.mjs","handlingStrategy":"validation","validationCode":"import fs from 'node:fs'\nimport path from 'node:path'\n\nfunction resolvePreferLocalTarget(external: string): string {\n  const nm = 'node_modules'\n  const target = path.basename(external) === nm\n    ? path.resolve(external)\n    : path.resolve(external, nm)\n  const stat = fs.lstatSync(target)\n  if (!stat.isDirectory()) throw new Error(`not a directory: ${target}`)\n  return target\n}\n\n// run before launching zx with --prefer-local\nresolvePreferLocalTarget(argv.preferLocal)","typeGuard":null,"tryCatchPattern":"import { Fail } from 'zx'\ntry {\n  await runScriptWithPreferLocal(path)\n} catch (e) {\n  if (e instanceof Fail && /doesn't exist or is not a directory/.test(e.message)) {\n    console.error('prefer-local target missing — run npm i in the external dir')\n  }\n  throw e\n}","preventionTips":["Always run the package manager install in the external directory before pointing --prefer-local at it.","Resolve --prefer-local to an absolute path and assert node_modules exists in a preflight check.","Treat an empty/missing node_modules as a setup failure, not a runtime retry."],"tags":["cli","node-modules","prefer-local","filesystem","symlink"],"backgroundTag":null,"analyzedSha":"00a2c484e219c2e84bfc3a199febf7fbce2cfbf4","analyzedAt":"2026-08-13T02:11:06.305Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}