vercel/turborepo · error · ConvertError
invalid_directory
invalid_directory
Error message
Could not find directory at ${workspaceRoot}. Ensure the directory exists. What it means
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.
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)
Example fix
// before
const project = await getWorkspaceDetails({ root: "apps/../my-repo" }); // typo -> throws
// after
import { resolve } from "node:path";
import { existsSync } from "node:fs";
const root = resolve("my-repo");
if (!existsSync(root)) throw new Error(`checkout missing: ${root}`);
const project = await getWorkspaceDetails({ root }); Defensive patterns
Strategy: validation
Validate before calling
import { existsSync, statSync } from "node:fs";
import { resolve } from "node:path";
function isExistingDirectory(root: string): boolean {
const abs = resolve(root);
return existsSync(abs) && statSync(abs).isDirectory();
}
// if (!isExistingDirectory(root)) fail fast with your own message; Type guard
function isDirectory(p: string): p is string {
try { return statSync(resolve(p)).isDirectory(); } catch { return false; }
} Try / catch
try {
const project = await getWorkspaceDetails({ root });
} catch (err) {
if (err instanceof ConvertError && err.type === "invalid_directory") {
throw new Error(`Workspace root missing: ${resolve(root)} - did checkout run?`, { cause: err });
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- package_manager-already_in_use
- error_removing_node_modules
- error_removing_node_modules
- package_manager-could_not_be_found
- package_manager-unsupported_version
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/afd01e747ef630b0.
Report an issue: GitHub.
Appendix: source
Thrown at packages/turbo-workspaces/src/get-workspace-details.ts:15
import { ConvertError } from "./errors";
import { MANAGERS } from "./managers";
import { directoryInfo } from "./utils";
import type { Project } from "./types";
export async function getWorkspaceDetails({
root
}: {
root: string;
}): Promise<Project> {
const { exists, absolute: workspaceRoot } = directoryInfo({
directory: root
});
if (!exists) {
throw new ConvertError(
`Could not find directory at ${workspaceRoot}. Ensure the directory exists.`,
{
type: "invalid_directory"
}
);
}
for (const { detect, read } of Object.values(MANAGERS)) {
// eslint-disable-next-line no-await-in-loop -- we want to run serially and bail on the first success
if (await detect({ workspaceRoot })) {
return read({ workspaceRoot });
}
}
throw new ConvertError(
"Could not determine package manager. Add `devEngines.packageManager` or legacy `packageManager` to `package.json`, or ensure a lockfile is present.",
{
type: "package_manager-unable_to_detect"View on GitHub (pinned to f9245100cf)