vercel/turborepo · error · ConvertError
error_removing_node_modules
error_removing_node_modules
Error message
Failed to remove node_modules
What it means
During bun removal (a step of converting away from bun), all root and workspace node_modules directories are deleted with fs.rm(dir, { recursive: true, force: true }). Any rejection is rethrown as ConvertError type error_removing_node_modules; the underlying errno (EBUSY/EPERM on Windows, EACCES on Unix) is discarded by the wrapper.
Source
Thrown at packages/turbo-workspaces/src/managers/bun.ts:216
});
if (!options?.dry) {
fs.writeJSONSync(project.paths.packageJson, packageJson, { spaces: 2 });
// collect all workspace node_modules directories
const allModulesDirs = [
project.paths.nodeModules,
...project.workspaceData.workspaces.map((w) => w.paths.nodeModules)
];
try {
logger.subStep(`removing "node_modules"`);
await Promise.all(
allModulesDirs.map((dir) =>
fs.rm(dir, { recursive: true, force: true })
)
);
} catch (err) {
throw new ConvertError("Failed to remove node_modules", {
type: "error_removing_node_modules"
});
}
}
}
/**
* Clean is called post install, and is used to clean up any files
* from this package manager that were needed for install,
* but not required after migration
*/
// eslint-disable-next-line @typescript-eslint/require-await -- must match the clean type signature
async function clean(args: CleanArgs): Promise<void> {
const { project, logger, options } = args;
logger.subStep(
`removing ${path.relative(project.paths.root, project.paths.lockfile)}`
);View on GitHub (pinned to 9f94a7d215)
Solutions
- Stop dev servers, editors, and watchers, then re-run the conversion
- Remove leftovers manually: rm -rf node_modules and each workspace's node_modules listed in project.workspaceData, then retry
- Fix ownership/permissions (chown -R $(whoami) node_modules) if another user/container created them
- On Windows, wait a few seconds for handles to be released and retry
Defensive patterns
Strategy: try-catch
Validate before calling
import { access, constants } from "node:fs/promises";
async function isRemovable(dir: string): Promise<boolean> {
try {
await access(dir, constants.W_OK);
return true;
} catch {
return false;
}
}
for (const dir of [project.paths.nodeModules, ...project.workspaceData.workspaces.map((w) => w.paths.nodeModules)]) {
if (!(await isRemovable(dir))) console.warn(`Not removable: ${dir}`);
} Type guard
function isNodeModulesRemovalError(e: unknown): boolean {
return e instanceof ConvertError && e.type === "error_removing_node_modules";
} Try / catch
try {
await convertProject({ project, convertTo, logger });
} catch (e) {
if (e instanceof ConvertError && e.type === "error_removing_node_modules") {
// kill dev servers/editors, then finish cleanup manually:
// rm -rf node_modules <each workspace>/node_modules
// and re-run with options.skipInstall if the conversion already wrote its files
} else throw e;
} Prevention
- Stop dev servers, editors, and file watchers before converting package managers
- On Windows, expect locked handles — retry removal after a short delay
- Ensure the converting process owns node_modules (avoid converting trees created by root/Docker)
When it happens
Trigger: fs.rm failing on any node_modules path: files locked by a running dev server, editor, or antivirus (Windows EBUSY/EPERM); directories owned by another user (root-created node_modules from Docker); a concurrent install touching the same tree.
Common situations: Converting while `next dev`/VS Code's TS server holds files open; converting in WSL against /mnt/c where file locks behave badly; node_modules created by root in CI then manipulated as a normal user.
Related errors
- Unable to write .gitignore
- Unable to write package.json
- Unable to update README.md
- package_manager-unexpected
- bun-workspace_glob_error
AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16).
Data as JSON: /api/errors/59381cba5d48eea6.
Report an issue: GitHub.