{"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/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-workspaces/src/get-workspace-details.ts#L1-L33","documentation":"getWorkspaceDetails() is the entry point of @turbo/workspaces: it resolves the given root via directoryInfo() and throws ConvertError type invalid_directory if the path does not exist, before any package manager detection runs. The message shows the resolved absolute path.","triggerScenarios":"Calling getWorkspaceDetails({ root }) with a nonexistent directory (relative roots are resolved against the process cwd first), or calling it before the target directory has been created.","commonSituations":"Scaffolding tools computing a target directory before mkdir; typos in a --path flag; race conditions where the caller creates the directory after the read.","solutions":["Create the directory first: fs.mkdirSync(root, { recursive: true })","Verify the absolute path exists before calling (fs.existsSync(path.resolve(root)))","Fix the typo or flag value that produced the wrong path"],"exampleFix":"// before\nconst project = await getWorkspaceDetails({ root }); // root not created yet\n\n// after\nfs.mkdirSync(root, { recursive: true });\nconst project = await getWorkspaceDetails({ root });","handlingStrategy":"validation","validationCode":"import { existsSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nconst absRoot = path.resolve(root);\nif (!existsSync(absRoot)) mkdirSync(absRoot, { recursive: true });\nconst project = await getWorkspaceDetails({ root: absRoot });","typeGuard":"function isInvalidDirectoryError(e: unknown): boolean {\n  return e instanceof ConvertError && e.type === \"invalid_directory\";\n}","tryCatchPattern":"try {\n  await getWorkspaceDetails({ root });\n} catch (e) {\n  if (e instanceof ConvertError && e.type === \"invalid_directory\") {\n    // message contains the resolved path; re-prompt or create the directory and retry\n  } else throw e;\n}","preventionTips":["Always path.resolve() user-supplied roots before workspace APIs","Create target directories with recursive mkdir before reading workspace details","Validate CLI path flags early with a clear usage error"],"tags":["filesystem","workspace","path","turbo-workspaces"],"backgroundTag":"directory-not-found","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}