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
- Go through getWorkspaceDetails so detection picks the correct manager
- If calling read directly, always pair with detect() and skip when false
- Generate the lockfile: run npm install once and commit package-lock.json (ensure package-lock is not disabled)
- 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
- Route through getWorkspaceDetails instead of hardcoded manager modules
- Ensure package-lock.json exists (run npm install once) before reading as npm
- Declare packageManager in package.json for deterministic detection
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
- package_manager-already_in_use
- package_manager-could_not_be_found
- package_manager-unexpected
- package_manager-unexpected
- package_manager-unsupported_version
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)