{"record":{"id":"59381cba5d48eea6","repo":"vercel/turborepo","slug":"error-removing-node-modules","errorCode":"error_removing_node_modules","errorMessage":"Failed to remove node_modules","messagePattern":"Failed to remove node_modules","errorType":"exception","errorClass":"ConvertError","httpStatus":null,"severity":"error","filePath":"packages/turbo-workspaces/src/managers/bun.ts","lineNumber":216,"sourceCode":"  });\n\n  if (!options?.dry) {\n    fs.writeJSONSync(project.paths.packageJson, packageJson, { spaces: 2 });\n\n    // collect all workspace node_modules directories\n    const allModulesDirs = [\n      project.paths.nodeModules,\n      ...project.workspaceData.workspaces.map((w) => w.paths.nodeModules)\n    ];\n    try {\n      logger.subStep(`removing \"node_modules\"`);\n      await Promise.all(\n        allModulesDirs.map((dir) =>\n          fs.rm(dir, { recursive: true, force: true })\n        )\n      );\n    } catch (err) {\n      throw new ConvertError(\"Failed to remove node_modules\", {\n        type: \"error_removing_node_modules\"\n      });\n    }\n  }\n}\n\n/**\n * Clean is called post install, and is used to clean up any files\n * from this package manager that were needed for install,\n * but not required after migration\n */\n// eslint-disable-next-line @typescript-eslint/require-await -- must match the clean type signature\nasync function clean(args: CleanArgs): Promise<void> {\n  const { project, logger, options } = args;\n\n  logger.subStep(\n    `removing ${path.relative(project.paths.root, project.paths.lockfile)}`\n  );","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/vercel/turborepo/blob/f9245100cf0d31d96628804ead485f6bf226e55a/packages/turbo-workspaces/src/managers/bun.ts#L198-L234","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import { accessSync, constants } from \"node:fs\";\n\nfunction isRemovable(dir: string): boolean {\n  try {\n    accessSync(dir, constants.W_OK);\n    return true;\n  } catch {\n    return false;\n  }\n}\n// check node_modules dirs are writable/absent before starting conversion","typeGuard":null,"tryCatchPattern":"try {\n  await convert({ project, convertTo: bunDetails, logger });\n} catch (err) {\n  if (err instanceof ConvertError && err.type === \"error_removing_node_modules\") {\n    await new Promise((r) => setTimeout(r, 2000)); // let Windows release handles\n    // close lockers, optionally npx rimraf node_modules, then retry conversion once\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["filesystem","bun","node-modules","windows","convert"],"backgroundTag":"file-remove-failed","analyzedSha":"f9245100cf0d31d96628804ead485f6bf226e55a","analyzedAt":"2026-08-17T10:46:15.696Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}