vercel/turborepo · error · ConvertError

package_manager-unexpected

package_manager-unexpected

Error message

Not an npm project

What it means

The npm manager's read() guard: it re-runs detect (package-lock.json exists or packageManager === 'npm') and throws ConvertError type package_manager-unexpected if false. Like the other managers, read() is meant to be called only after getWorkspaceDetails' detect loop has selected npm; calling it on a non-npm workspace is an API contract violation, not a user config problem.

Solutions

  1. Go through getWorkspaceDetails so detection picks the correct manager
  2. If calling read directly, always pair with detect() and skip when false
  3. Generate the lockfile: run npm install once and commit package-lock.json (ensure package-lock is not disabled)
  4. Declare "packageManager": "npm@x.y.z" in package.json for lockfile-free detection

Example fix

// before
const project = await MANAGERS.npm.read({ workspaceRoot }); // throws "Not an npm project"

// after
if (await MANAGERS.npm.detect({ workspaceRoot })) {
  const project = await MANAGERS.npm.read({ workspaceRoot });
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(await MANAGERS.npm.detect({ workspaceRoot }))) {
  throw new Error("Workspace is not an npm project (no package-lock.json, no packageManager field)");
}
const project = await MANAGERS.npm.read({ workspaceRoot });

Type guard

async function readIfNpm(args: { workspaceRoot: string }): Promise<Project | null> {
  return (await MANAGERS.npm.detect(args)) ? MANAGERS.npm.read(args) : null;
}

Try / catch

try {
  const project = await MANAGERS.npm.read({ workspaceRoot });
} catch (err) {
  if (err instanceof ConvertError && err.type === "package_manager-unexpected") {
    const project = await getWorkspaceDetails({ root: workspaceRoot }); // correct detect/read pairing
  } else throw err;
}

Prevention

When it happens

Trigger: Directly calling managers/npm read({ workspaceRoot }) on a workspace without package-lock.json and without "packageManager": "npm@..." - e.g. custom scripts importing the manager module, or the lockfile being absent/gitignored so detection cannot succeed.

Common situations: Migration tooling that hardcodes the npm manager instead of detecting, fresh repos where npm install never ran (no lockfile), lockfiles excluded via .gitignore or .npmrc package-lock=false, or tests without fixtures.

Related errors


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/a2b01704c5bb086e. Report an issue: GitHub.

Appendix: source

Thrown at packages/turbo-workspaces/src/managers/npm.ts:57

 */
// eslint-disable-next-line @typescript-eslint/require-await -- must match the detect type signature
async function detect(args: DetectArgs): Promise<boolean> {
  const lockFile = path.join(args.workspaceRoot, PACKAGE_MANAGER_DETAILS.lock);
  const packageManager = getWorkspacePackageManager({
    workspaceRoot: args.workspaceRoot
  });
  return (
    fs.existsSync(lockFile) || packageManager === PACKAGE_MANAGER_DETAILS.name
  );
}

/**
  Read workspace data from npm workspaces into generic format
*/
async function read(args: ReadArgs): Promise<Project> {
  const isNpm = await detect(args);
  if (!isNpm) {
    throw new ConvertError("Not an npm project", {
      type: "package_manager-unexpected"
    });
  }

  const packageJson = getPackageJson(args);
  const { name, description } = getWorkspaceInfo(args);
  const workspaceGlobs = parseWorkspacePackages({
    workspaces: packageJson.workspaces
  });
  return {
    name,
    description,
    packageManager: PACKAGE_MANAGER_DETAILS.name,
    paths: expandPaths({
      root: args.workspaceRoot,
      lockFile: PACKAGE_MANAGER_DETAILS.lock
    }),
    workspaceData: {

View on GitHub (pinned to f9245100cf)