vercel/turborepo · error · ConvertError

package_manager-unexpected

package_manager-unexpected

Error message

Not a nub project

What it means

Thrown by the nub manager handler's read() in turbo-workspaces when nub's detect() returns false. Unlike npm/yarn/pnpm, nub detection checks ONLY the packageManager field ("packageManager": "nub@x.y.z" or devEngines.packageManager with name "nub") - the presence of nub.lock, lock.yaml, or any other lockfile alone never makes a project detect as nub. ConvertError type "package_manager-unexpected".

Source

Thrown at packages/turbo-workspaces/src/managers/nub.ts:63

  bun
} as const;

/**
 * nub is recognized only through the `packageManager` field in `package.json`
 * (`"nub@x.y.z"`). A foreign lockfile alone does not imply nub.
 */
// eslint-disable-next-line @typescript-eslint/require-await -- must match the detect type signature
async function detect(args: DetectArgs): Promise<boolean> {
  const packageManager = getWorkspacePackageManager({
    workspaceRoot: args.workspaceRoot
  });
  return packageManager === PACKAGE_MANAGER_DETAILS.name;
}

async function read(args: ReadArgs): Promise<Project> {
  const isNub = await detect(args);
  if (!isNub) {
    throw new ConvertError("Not a nub project", {
      type: "package_manager-unexpected"
    });
  }

  const underlying = getUnderlyingLockfileManager({
    workspaceRoot: args.workspaceRoot
  });
  const underlyingHandler = UNDERLYING_MANAGERS[underlying];

  if (await underlyingHandler.detect(args)) {
    const project = await underlyingHandler.read(args);
    return { ...project, packageManager: PACKAGE_MANAGER_DETAILS.name };
  }

  const packageJson = getPackageJson(args);
  const { name, description } = getWorkspaceInfo(args);
  const lockfile = getUnderlyingLockfileName({
    workspaceRoot: args.workspaceRoot

View on GitHub (pinned to 9f94a7d215)

Solutions

  1. Set "packageManager": "nub@<version>" (or devEngines.packageManager: { "name": "nub", "version": "<semver range>" }) in the root package.json, then retry
  2. Verify the spelling - the field must match /^.+@.+$/ and the name must be exactly one of npm, pnpm, yarn, bun, nub, aube (getWorkspacePackageManager returns undefined otherwise and detection silently fails)
  3. Use getWorkspaceDetails({ root }) instead of MANAGERS.nub.read directly so whichever manager actually matches is the one that reads
  4. Avoid editing package.json while a convert/migration is in progress

Example fix

// root package.json - before
{
  "name": "monorepo"
}

// after - makes nub.detect() true, so nub.read() no longer throws
{
  "name": "monorepo",
  "packageManager": "nub@0.9.5"
}
Defensive patterns

Strategy: validation

Validate before calling

import { MANAGERS } from "turbo-workspaces";

// nub is detectable ONLY via the packageManager field - check it explicitly
const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
declaresNub = pkg.packageManager?.startsWith("nub@") ||
  pkg.devEngines?.packageManager?.name === "nub";
if (!declaresNub) {
  throw new Error('add "packageManager": "nub@<version>" to package.json before reading as nub');
}
const project = await MANAGERS.nub.read({ workspaceRoot: root });

Type guard

import { ConvertError } from "turbo-workspaces";

function isPackageManagerUnexpected(err: unknown): err is ConvertError {
  return err instanceof ConvertError && err.type === "package_manager-unexpected";
}

Try / catch

try {
  const project = await MANAGERS.nub.read({ workspaceRoot: root });
} catch (err) {
  if (err instanceof ConvertError && err.type === "package_manager-unexpected") {
    project = await getWorkspaceDetails({ root }); // probe all managers instead
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling MANAGERS.nub.read({ workspaceRoot }) (or having convertProject target a project whose detected manager chain reaches nub.read) when the root package.json declares pnpm/npm/yarn/bun in packageManager/devEngines.packageManager, or declares nothing; also when a concurrent edit removes or changes the packageManager field between detect() and read().

Common situations: Repos with a nub.lock or lock.yaml checked in but no packageManager declaration (detection then falls through to pnpm/yarn/npm based on the lockfile); switching the declaration while tooling is running; copy-pasting read code from the npm/pnpm handlers where lockfile presence would have been enough and assuming nub behaves the same.

Related errors


AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16). Data as JSON: /api/errors/c026d7867051d08a. Report an issue: GitHub.