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

  1. Verify the absolute path printed in the message with ls
  2. Pass an absolute root: path.resolve(root), and confirm the process cwd
  3. If the project lives elsewhere, fix the root parameter/config that produced it
  4. 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

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


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)