{"record":{"id":"c026d7867051d08a","repo":"vercel/turborepo","slug":"package-manager-unexpected-c026d7","errorCode":"package_manager-unexpected","errorMessage":"Not a nub project","messagePattern":"Not a nub project","errorType":"exception","errorClass":"ConvertError","httpStatus":null,"severity":"error","filePath":"packages/turbo-workspaces/src/managers/nub.ts","lineNumber":63,"sourceCode":"  bun\n} as const;\n\n/**\n * nub is recognized only through the `packageManager` field in `package.json`\n * (`\"nub@x.y.z\"`). A foreign lockfile alone does not imply nub.\n */\n// eslint-disable-next-line @typescript-eslint/require-await -- must match the detect type signature\nasync function detect(args: DetectArgs): Promise<boolean> {\n  const packageManager = getWorkspacePackageManager({\n    workspaceRoot: args.workspaceRoot\n  });\n  return packageManager === PACKAGE_MANAGER_DETAILS.name;\n}\n\nasync function read(args: ReadArgs): Promise<Project> {\n  const isNub = await detect(args);\n  if (!isNub) {\n    throw new ConvertError(\"Not a nub project\", {\n      type: \"package_manager-unexpected\"\n    });\n  }\n\n  const underlying = getUnderlyingLockfileManager({\n    workspaceRoot: args.workspaceRoot\n  });\n  const underlyingHandler = UNDERLYING_MANAGERS[underlying];\n\n  if (await underlyingHandler.detect(args)) {\n    const project = await underlyingHandler.read(args);\n    return { ...project, packageManager: PACKAGE_MANAGER_DETAILS.name };\n  }\n\n  const packageJson = getPackageJson(args);\n  const { name, description } = getWorkspaceInfo(args);\n  const lockfile = getUnderlyingLockfileName({\n    workspaceRoot: args.workspaceRoot","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/vercel/turborepo/blob/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-workspaces/src/managers/nub.ts#L45-L81","documentation":"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\".","triggerScenarios":"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().","commonSituations":"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.","solutions":["Set \"packageManager\": \"nub@<version>\" (or devEngines.packageManager: { \"name\": \"nub\", \"version\": \"<semver range>\" }) in the root package.json, then retry","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)","Use getWorkspaceDetails({ root }) instead of MANAGERS.nub.read directly so whichever manager actually matches is the one that reads","Avoid editing package.json while a convert/migration is in progress"],"exampleFix":"// root package.json - before\n{\n  \"name\": \"monorepo\"\n}\n\n// after - makes nub.detect() true, so nub.read() no longer throws\n{\n  \"name\": \"monorepo\",\n  \"packageManager\": \"nub@0.9.5\"\n}","handlingStrategy":"validation","validationCode":"import { MANAGERS } from \"turbo-workspaces\";\n\n// nub is detectable ONLY via the packageManager field - check it explicitly\nconst pkg = JSON.parse(readFileSync(path.join(root, \"package.json\"), \"utf8\"));\ndeclaresNub = pkg.packageManager?.startsWith(\"nub@\") ||\n  pkg.devEngines?.packageManager?.name === \"nub\";\nif (!declaresNub) {\n  throw new Error('add \"packageManager\": \"nub@<version>\" to package.json before reading as nub');\n}\nconst project = await MANAGERS.nub.read({ workspaceRoot: root });","typeGuard":"import { ConvertError } from \"turbo-workspaces\";\n\nfunction isPackageManagerUnexpected(err: unknown): err is ConvertError {\n  return err instanceof ConvertError && err.type === \"package_manager-unexpected\";\n}","tryCatchPattern":"try {\n  const project = await MANAGERS.nub.read({ workspaceRoot: root });\n} catch (err) {\n  if (err instanceof ConvertError && err.type === \"package_manager-unexpected\") {\n    project = await getWorkspaceDetails({ root }); // probe all managers instead\n  } else {\n    throw err;\n  }\n}","preventionTips":["Remember nub ignores lockfiles: only the packageManager declaration makes a project nub","Keep the declaration committed and spelled exactly (name@version regex)","Use getWorkspaceDetails for discovery; reserve MANAGERS.nub.read for after a positive detect","Do not edit package.json while migration tooling is running"],"tags":["nub","workspaces","package-manager-detection","migration","convert"],"backgroundTag":"wrong-package-manager","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}