{"record":{"id":"afd01e747ef630b0","repo":"vercel/turborepo","slug":"invalid-directory","errorCode":"invalid_directory","errorMessage":"Could not find directory at ${workspaceRoot}. Ensure the directory exists.","messagePattern":"Could not find directory at (.+?)\\. Ensure the directory exists\\.","errorType":"exception","errorClass":"ConvertError","httpStatus":null,"severity":"error","filePath":"packages/turbo-workspaces/src/get-workspace-details.ts","lineNumber":15,"sourceCode":"import { ConvertError } from \"./errors\";\nimport { MANAGERS } from \"./managers\";\nimport { directoryInfo } from \"./utils\";\nimport type { Project } from \"./types\";\n\nexport async function getWorkspaceDetails({\n  root\n}: {\n  root: string;\n}): Promise<Project> {\n  const { exists, absolute: workspaceRoot } = directoryInfo({\n    directory: root\n  });\n  if (!exists) {\n    throw new ConvertError(\n      `Could not find directory at ${workspaceRoot}. Ensure the directory exists.`,\n      {\n        type: \"invalid_directory\"\n      }\n    );\n  }\n\n  for (const { detect, read } of Object.values(MANAGERS)) {\n    // eslint-disable-next-line no-await-in-loop -- we want to run serially and bail on the first success\n    if (await detect({ workspaceRoot })) {\n      return read({ workspaceRoot });\n    }\n  }\n\n  throw new ConvertError(\n    \"Could not determine package manager. Add `devEngines.packageManager` or legacy `packageManager` to `package.json`, or ensure a lockfile is present.\",\n    {\n      type: \"package_manager-unable_to_detect\"","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/vercel/turborepo/blob/f9245100cf0d31d96628804ead485f6bf226e55a/packages/turbo-workspaces/src/get-workspace-details.ts#L1-L33","documentation":"getWorkspaceDetails resolves the passed root and checks existence first; a nonexistent directory throws ConvertError type invalid_directory before any manager detection runs. The message includes the absolute path that was checked, so a relative/typo'd root is immediately visible.","triggerScenarios":"Calling getWorkspaceDetails({ root }) where root does not exist on disk: a typo, a relative path interpreted against an unexpected cwd, a directory deleted between checks, or an CI checkout step that did not run.","commonSituations":"Scripts running from a different working directory than assumed, monorepo tooling passing a workspace path computed from config that no longer exists, race with git clean, or Windows/WSL path mismatches (C:\\ vs /mnt/c).","solutions":["Verify the absolute path printed in the message with ls","Pass an absolute root: path.resolve(root), and confirm the process cwd","If the project lives elsewhere, fix the root parameter/config that produced it","On WWindows/WSL, check path format consistency (drive letters vs POSIX mounts)"],"exampleFix":"// before\nconst project = await getWorkspaceDetails({ root: \"apps/../my-repo\" }); // typo -> throws\n\n// after\nimport { resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nconst root = resolve(\"my-repo\");\nif (!existsSync(root)) throw new Error(`checkout missing: ${root}`);\nconst project = await getWorkspaceDetails({ root });","handlingStrategy":"validation","validationCode":"import { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nfunction isExistingDirectory(root: string): boolean {\n  const abs = resolve(root);\n  return existsSync(abs) && statSync(abs).isDirectory();\n}\n// if (!isExistingDirectory(root)) fail fast with your own message;","typeGuard":"function isDirectory(p: string): p is string {\n  try { return statSync(resolve(p)).isDirectory(); } catch { return false; }\n}","tryCatchPattern":"try {\n  const project = await getWorkspaceDetails({ root });\n} catch (err) {\n  if (err instanceof ConvertError && err.type === \"invalid_directory\") {\n    throw new Error(`Workspace root missing: ${resolve(root)} - did checkout run?`, { cause: err });\n  }\n  throw err;\n}","preventionTips":["Always pass an absolute root (path.resolve) to workspace APIs","Verify CI checkout steps completed before workspace tooling runs","Normalize WSL/Windows path formats before crossing environment boundaries"],"tags":["filesystem","validation","convert","turbo-workspaces"],"backgroundTag":"directory-not-found","analyzedSha":"f9245100cf0d31d96628804ead485f6bf226e55a","analyzedAt":"2026-08-17T10:46:15.696Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}