vercel/turborepo · error · ConvertError
error_removing_node_modules
error_removing_node_modules
Error message
Failed to remove node_modules
What it means
During bun conversion, remove deletes node_modules at the workspace root and in every workspace package via fs.rm(recursive, force). Any rejection (EBUSY/EPERM on Windows, EACCES, ENOTEMPTY races) is rethrown as ConvertError type error_removing_node_modules. force:true already ignores missing entries, so this throw means the OS genuinely refused the delete.
Solutions
- Close editors/terminals/dev servers watching the repo, then rerun the conversion
- On Windows, wait a few seconds after killing processes (AV/indexer release handles late) and retry
- Delete manually as a workaround: npx rimraf node_modules **/node_modules or rd /s /q node_modules (cmd)
- Fix ownership/permissions if EACCES: check directory ownership in CI containers
- Retry once before treating as fatal; EBUSY on Windows is frequently transient
Defensive patterns
Strategy: retry
Validate before calling
import { accessSync, constants } from "node:fs";
function isRemovable(dir: string): boolean {
try {
accessSync(dir, constants.W_OK);
return true;
} catch {
return false;
}
}
// check node_modules dirs are writable/absent before starting conversion Try / catch
try {
await convert({ project, convertTo: bunDetails, logger });
} catch (err) {
if (err instanceof ConvertError && err.type === "error_removing_node_modules") {
await new Promise((r) => setTimeout(r, 2000)); // let Windows release handles
// close lockers, optionally npx rimraf node_modules, then retry conversion once
}
throw err;
} Prevention
- Close editors/dev servers/ts-server before converting package managers
- On Windows, expect transient EBUSY - retry once after a short delay
- Pre-delete node_modules yourself (npx rimraf) so the tool has nothing to remove
When it happens
Trigger: Converting to bun when any node_modules directory cannot be deleted: Windows EBUSY because a file inside is open (editor, dev server, antivirus), EACCES when the tree is owned by another user (containers, sudo-installed modules), or ENOTEMPTY when files change during the recursive delete.
Common situations: Windows file locking by editors/termines/ts-server in the project, OneDrive/Dropbox sync holding handles, CI runners where a previous step left watchers running, or shared hosts with restrictive permissions on node_modules.
Related errors
- error_removing_node_modules
- invalid_directory
- anchored system path is relative
- daemon socket parent is not a directory
- daemon socket path did not have an owner SID
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/59381cba5d48eea6.
Report an issue: GitHub.
Appendix: 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 f9245100cf)