codex-multi-auth 2.8.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +3 -1
  3. package/dist/lib/accounts.d.ts +15 -0
  4. package/dist/lib/accounts.d.ts.map +1 -1
  5. package/dist/lib/accounts.js +126 -21
  6. package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
  7. package/dist/lib/codex-manager/account-pool-write.js +4 -0
  8. package/dist/lib/codex-manager/backend-settings-schema.js +1 -1
  9. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  10. package/dist/lib/codex-manager/commands/status.js +5 -1
  11. package/dist/lib/codex-manager/commands/uninstall.d.ts +1 -0
  12. package/dist/lib/codex-manager/commands/uninstall.d.ts.map +1 -1
  13. package/dist/lib/codex-manager/commands/uninstall.js +12 -1
  14. package/dist/lib/codex-manager/commands/why-selected.d.ts +1 -1
  15. package/dist/lib/codex-manager/commands/why-selected.d.ts.map +1 -1
  16. package/dist/lib/codex-manager/commands/why-selected.js +1 -1
  17. package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -1
  18. package/dist/lib/codex-manager/formatters/quota-formatters.js +32 -2
  19. package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
  20. package/dist/lib/codex-manager/health-check.js +19 -3
  21. package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
  22. package/dist/lib/codex-manager/login-oauth.js +13 -7
  23. package/dist/lib/codex-manager.d.ts +4 -0
  24. package/dist/lib/codex-manager.d.ts.map +1 -1
  25. package/dist/lib/codex-manager.js +50 -16
  26. package/dist/lib/config.d.ts +3 -1
  27. package/dist/lib/config.d.ts.map +1 -1
  28. package/dist/lib/config.js +3 -1
  29. package/dist/lib/forecast.d.ts.map +1 -1
  30. package/dist/lib/forecast.js +7 -1
  31. package/dist/lib/index.d.ts +1 -0
  32. package/dist/lib/index.d.ts.map +1 -1
  33. package/dist/lib/index.js +1 -0
  34. package/dist/lib/parallel-probe.d.ts.map +1 -1
  35. package/dist/lib/parallel-probe.js +3 -1
  36. package/dist/lib/policy/runtime-policy.d.ts +1 -0
  37. package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
  38. package/dist/lib/policy/runtime-policy.js +7 -0
  39. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
  40. package/dist/lib/preemptive-quota-scheduler.js +35 -15
  41. package/dist/lib/rotation.d.ts +4 -0
  42. package/dist/lib/rotation.d.ts.map +1 -1
  43. package/dist/lib/rotation.js +13 -6
  44. package/dist/lib/runtime/app-bind.d.ts +40 -0
  45. package/dist/lib/runtime/app-bind.d.ts.map +1 -1
  46. package/dist/lib/runtime/app-bind.js +550 -20
  47. package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
  48. package/dist/lib/runtime/rotation-account-selection.js +2 -0
  49. package/dist/lib/runtime/rotation-proxy-state.d.ts +2 -0
  50. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  51. package/dist/lib/runtime/rotation-token-refresh.d.ts.map +1 -1
  52. package/dist/lib/runtime/rotation-token-refresh.js +3 -0
  53. package/dist/lib/runtime-constants.d.ts +2 -0
  54. package/dist/lib/runtime-constants.d.ts.map +1 -1
  55. package/dist/lib/runtime-constants.js +2 -0
  56. package/dist/lib/runtime-rotation-proxy.d.ts +8 -0
  57. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  58. package/dist/lib/runtime-rotation-proxy.js +59 -5
  59. package/dist/lib/schemas.d.ts +15 -0
  60. package/dist/lib/schemas.d.ts.map +1 -1
  61. package/dist/lib/schemas.js +3 -0
  62. package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
  63. package/dist/lib/storage/flagged-storage.js +15 -0
  64. package/dist/lib/storage/path-state.d.ts +1 -0
  65. package/dist/lib/storage/path-state.d.ts.map +1 -1
  66. package/dist/lib/storage/path-state.js +30 -3
  67. package/dist/lib/storage/public-types.d.ts +6 -0
  68. package/dist/lib/storage/public-types.d.ts.map +1 -1
  69. package/dist/lib/storage.d.ts.map +1 -1
  70. package/dist/lib/storage.js +17 -4
  71. package/package.json +198 -198
  72. package/scripts/codex-app-router.js +47 -3
  73. package/scripts/codex.js +165 -21
  74. package/scripts/install-codex-auth-utils.js +9 -2
  75. package/scripts/postinstall.js +2 -0
  76. package/scripts/preuninstall.js +11 -0
package/scripts/codex.js CHANGED
@@ -80,6 +80,8 @@ const APP_SERVER_CONFIG_ARGS_ENV =
80
80
  "CODEX_MULTI_AUTH_APP_SERVER_CONFIG_ARGS_JSON";
81
81
  const APP_RUNTIME_HELPER_STATUS_FILE =
82
82
  RUNTIME_CONSTANTS.APP_RUNTIME_HELPER_STATUS_FILE;
83
+ const APP_RUNTIME_HELPER_OWNER_FILE =
84
+ RUNTIME_CONSTANTS.APP_RUNTIME_HELPER_OWNER_FILE;
83
85
  const DEFAULT_APP_RUNTIME_HELPER_IDLE_MS = 12 * 60 * 60 * 1000;
84
86
  const DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS = 5_000;
85
87
  const APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS = 15_000;
@@ -110,6 +112,17 @@ let shadowHomeSyncLockOwnerWriteFailuresRemaining = Number.parseInt(
110
112
  process.env.CODEX_MULTI_AUTH_TEST_SHADOW_LOCK_OWNER_WRITE_FAILURES ?? "0",
111
113
  10,
112
114
  );
115
+ let appServerShimFileCleanupBusyFailuresRemaining =
116
+ Number.parseInt(
117
+ process.env.CODEX_MULTI_AUTH_TEST_APP_SERVER_SHIM_FILE_CLEANUP_BUSY_FAILURES ??
118
+ "0",
119
+ 10,
120
+ ) || 0;
121
+ let appServerShimCopyBusyFailuresRemaining =
122
+ Number.parseInt(
123
+ process.env.CODEX_MULTI_AUTH_TEST_APP_SERVER_SHIM_COPY_BUSY_FAILURES ?? "0",
124
+ 10,
125
+ ) || 0;
113
126
  const shadowHomeCleanupRetryMarkerDir =
114
127
  (process.env.CODEX_MULTI_AUTH_TEST_SHADOW_RETRY_MARKER_DIR ?? "").trim();
115
128
  let warnedInvalidRuntimeRotationProxyEnv = false;
@@ -122,6 +135,7 @@ async function loadRuntimeConstants() {
122
135
  const fallback = {
123
136
  RUNTIME_ROTATION_PROXY_PROVIDER_ID: `${APP_SERVER_ACCOUNT_DISPLAY_NAME}-runtime-proxy`,
124
137
  APP_RUNTIME_HELPER_STATUS_FILE: "runtime-rotation-app-helper.json",
138
+ APP_RUNTIME_HELPER_OWNER_FILE: "runtime-rotation-app-helper-owner.json",
125
139
  };
126
140
  try {
127
141
  const mod = await import("../dist/lib/runtime-constants.js");
@@ -134,6 +148,10 @@ async function loadRuntimeConstants() {
134
148
  typeof mod.APP_RUNTIME_HELPER_STATUS_FILE === "string"
135
149
  ? mod.APP_RUNTIME_HELPER_STATUS_FILE
136
150
  : fallback.APP_RUNTIME_HELPER_STATUS_FILE,
151
+ APP_RUNTIME_HELPER_OWNER_FILE:
152
+ typeof mod.APP_RUNTIME_HELPER_OWNER_FILE === "string"
153
+ ? mod.APP_RUNTIME_HELPER_OWNER_FILE
154
+ : fallback.APP_RUNTIME_HELPER_OWNER_FILE,
137
155
  };
138
156
  } catch {
139
157
  // Keep wrapper startup resilient when dist has not been built yet.
@@ -171,6 +189,39 @@ function removeDirectoryWithRetry(targetPath) {
171
189
  }
172
190
  }
173
191
 
192
+ function withSynchronousFileOperationRetry(operation) {
193
+ for (
194
+ let attempt = 0;
195
+ attempt <= SHADOW_HOME_CLEANUP_BACKOFF_MS.length;
196
+ attempt += 1
197
+ ) {
198
+ try {
199
+ return operation();
200
+ } catch (error) {
201
+ if (
202
+ !isRetryableShadowHomeCleanupError(error) ||
203
+ attempt === SHADOW_HOME_CLEANUP_BACKOFF_MS.length
204
+ ) {
205
+ throw error;
206
+ }
207
+ sleepSync(SHADOW_HOME_CLEANUP_BACKOFF_MS[attempt]);
208
+ }
209
+ }
210
+ }
211
+
212
+ function maybeThrowSimulatedAppServerShimFileError(kind) {
213
+ const remaining =
214
+ kind === "copy"
215
+ ? appServerShimCopyBusyFailuresRemaining
216
+ : appServerShimFileCleanupBusyFailuresRemaining;
217
+ if (remaining <= 0) return;
218
+ if (kind === "copy") appServerShimCopyBusyFailuresRemaining -= 1;
219
+ else appServerShimFileCleanupBusyFailuresRemaining -= 1;
220
+ const error = new Error(`simulated app-server shim ${kind} EBUSY`);
221
+ error.code = "EBUSY";
222
+ throw error;
223
+ }
224
+
174
225
  /**
175
226
  * Best-effort async directory removal for hot-path callbacks (e.g. the
176
227
  * status-refresh child's close/error handlers, which fire on the PARENT event
@@ -3653,14 +3704,35 @@ function installRuntimeRotationAppServerCliShim(forwardedEnv, configArgs = []) {
3653
3704
  const preloadPath = join(shimDir, "codex-multi-auth-app-server-preload.mjs");
3654
3705
  try {
3655
3706
  try {
3656
- rmSync(executablePath, { force: true });
3707
+ withSynchronousFileOperationRetry(() => {
3708
+ maybeThrowSimulatedAppServerShimFileError("cleanup");
3709
+ rmSync(executablePath, { force: true });
3710
+ });
3657
3711
  } catch {
3658
- // Best-effort stale shim cleanup only.
3712
+ // Best-effort stale shim cleanup only; the copy below will report a
3713
+ // persistent failure without leaving a partially-created helper.
3659
3714
  }
3660
- try {
3661
- linkSync(process.execPath, executablePath);
3662
- } catch {
3663
- copyFileSync(process.execPath, executablePath);
3715
+ if (
3716
+ process.platform === "win32" ||
3717
+ (process.env.CODEX_MULTI_AUTH_TEST_FORCE_APP_SERVER_SHIM_COPY ?? "") === "1"
3718
+ ) {
3719
+ // A Windows hard link to the running node.exe remains locked by the
3720
+ // helper process itself, which prevents the shim directory from being
3721
+ // removed during graceful helper shutdown. Use an independent image so
3722
+ // the helper can clean up its app-server shim before exiting.
3723
+ withSynchronousFileOperationRetry(() => {
3724
+ maybeThrowSimulatedAppServerShimFileError("copy");
3725
+ copyFileSync(process.execPath, executablePath);
3726
+ });
3727
+ } else {
3728
+ try {
3729
+ linkSync(process.execPath, executablePath);
3730
+ } catch {
3731
+ withSynchronousFileOperationRetry(() => {
3732
+ maybeThrowSimulatedAppServerShimFileError("copy");
3733
+ copyFileSync(process.execPath, executablePath);
3734
+ });
3735
+ }
3664
3736
  }
3665
3737
  if (process.platform !== "win32") {
3666
3738
  chmodSync(executablePath, 0o755);
@@ -3704,6 +3776,19 @@ function resolveRuntimeRotationAppHelperStatusPath(env = process.env) {
3704
3776
  return join(multiAuthDir, APP_RUNTIME_HELPER_STATUS_FILE);
3705
3777
  }
3706
3778
 
3779
+ function resolveRuntimeRotationAppHelperOwnerPath(env = process.env, helperPid) {
3780
+ const multiAuthDir =
3781
+ resolveOriginalMultiAuthDir(env) ?? join(resolveCodexHomeDir(env), "multi-auth");
3782
+ const ownerFileName =
3783
+ typeof helperPid === "number" && Number.isInteger(helperPid) && helperPid > 0
3784
+ ? APP_RUNTIME_HELPER_OWNER_FILE.replace(
3785
+ /\.json$/i,
3786
+ `.${helperPid}.json`,
3787
+ )
3788
+ : APP_RUNTIME_HELPER_OWNER_FILE;
3789
+ return join(multiAuthDir, ownerFileName);
3790
+ }
3791
+
3707
3792
  function writeOwnerOnlyJsonFileAtomicSync(targetPath, payload) {
3708
3793
  const targetDir = dirname(targetPath);
3709
3794
  mkdirSync(targetDir, { recursive: true });
@@ -3816,9 +3901,38 @@ function writeRuntimeRotationAppHelperStatus(payload, env = process.env) {
3816
3901
  }
3817
3902
  }
3818
3903
 
3904
+ function writeRuntimeRotationAppHelperOwner(
3905
+ identityToken,
3906
+ helperPid,
3907
+ env = process.env,
3908
+ ) {
3909
+ if (
3910
+ typeof helperPid !== "number" ||
3911
+ !Number.isInteger(helperPid) ||
3912
+ helperPid < 1
3913
+ ) {
3914
+ return;
3915
+ }
3916
+ try {
3917
+ writeOwnerOnlyJsonFileAtomicSync(
3918
+ resolveRuntimeRotationAppHelperOwnerPath(env, helperPid),
3919
+ {
3920
+ version: 1,
3921
+ kind: "codex-app-runtime-rotation-helper-owner",
3922
+ identityToken,
3923
+ launcherPid: process.pid,
3924
+ createdAt: Date.now(),
3925
+ },
3926
+ );
3927
+ } catch {
3928
+ // Best-effort metadata; an unavailable owner file must never stop the helper.
3929
+ }
3930
+ }
3931
+
3819
3932
  function createRuntimeRotationAppHelperStatus({
3820
3933
  proxyServer,
3821
3934
  startedAt,
3935
+ identityToken,
3822
3936
  idleTimeoutMs,
3823
3937
  lastActivityAt,
3824
3938
  state,
@@ -3838,6 +3952,8 @@ function createRuntimeRotationAppHelperStatus({
3838
3952
  kind: "codex-app-runtime-rotation-helper",
3839
3953
  state,
3840
3954
  pid: process.pid,
3955
+ scriptPath: process.argv[1] ?? null,
3956
+ identityToken: identityToken || null,
3841
3957
  startedAt,
3842
3958
  updatedAt: Date.now(),
3843
3959
  baseUrl: proxyServer?.baseUrl ?? null,
@@ -3855,7 +3971,7 @@ function createRuntimeRotationAppHelperStatus({
3855
3971
  };
3856
3972
  }
3857
3973
 
3858
- async function runRuntimeRotationAppHelper() {
3974
+ async function runRuntimeRotationAppHelper(identityToken = "") {
3859
3975
  let proxyServer = null;
3860
3976
  let runtimeContext = null;
3861
3977
  let appServerShimDir = null;
@@ -3872,6 +3988,7 @@ async function runRuntimeRotationAppHelper() {
3872
3988
  createRuntimeRotationAppHelperStatus({
3873
3989
  proxyServer,
3874
3990
  startedAt,
3991
+ identityToken,
3875
3992
  idleTimeoutMs,
3876
3993
  lastActivityAt,
3877
3994
  state,
@@ -4066,25 +4183,32 @@ function startRuntimeRotationAppHelper(baseContext, options = {}) {
4066
4183
  let stdoutBuffer = "";
4067
4184
  let stderrBuffer = "";
4068
4185
  let settled = false;
4186
+ const identityToken = randomBytes(24).toString("hex");
4187
+ const helperEnv = {
4188
+ ...baseContext.env,
4189
+ CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
4190
+ realCodexHome,
4191
+ baseContext.env,
4192
+ ),
4193
+ [APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
4194
+ [APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
4195
+ [APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV]:
4196
+ options.useCanonicalHome === true ? "1" : "0",
4197
+ };
4069
4198
  const helper = spawn(
4070
4199
  process.execPath,
4071
- [fileURLToPath(import.meta.url), INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG],
4200
+ [
4201
+ fileURLToPath(import.meta.url),
4202
+ INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG,
4203
+ identityToken,
4204
+ ],
4072
4205
  {
4073
- env: {
4074
- ...baseContext.env,
4075
- CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
4076
- realCodexHome,
4077
- baseContext.env,
4078
- ),
4079
- [APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
4080
- [APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
4081
- [APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV]:
4082
- options.useCanonicalHome === true ? "1" : "0",
4083
- },
4206
+ env: helperEnv,
4084
4207
  stdio: ["ignore", "pipe", "pipe"],
4085
4208
  detached: true,
4086
4209
  },
4087
4210
  );
4211
+ writeRuntimeRotationAppHelperOwner(identityToken, helper.pid, helperEnv);
4088
4212
  let timeout = null;
4089
4213
  const finish = (result) => {
4090
4214
  if (settled) return;
@@ -4150,6 +4274,11 @@ async function createRuntimeRotationAppHelperContext(
4150
4274
  options,
4151
4275
  );
4152
4276
  const helperEnv = message.env ?? {};
4277
+ const helperShimDir =
4278
+ typeof helperEnv.CODEX_CLI_PATH === "string" &&
4279
+ helperEnv.CODEX_CLI_PATH.length > 0
4280
+ ? helperEnv.CODEX_CLI_PATH
4281
+ : null;
4153
4282
  const helperArgs = Array.isArray(message.args)
4154
4283
  ? message.args.filter((arg) => typeof arg === "string")
4155
4284
  : [];
@@ -4163,7 +4292,22 @@ async function createRuntimeRotationAppHelperContext(
4163
4292
  helper.unref();
4164
4293
  return;
4165
4294
  }
4166
- await stopRuntimeRotationAppHelper(helper);
4295
+ try {
4296
+ await stopRuntimeRotationAppHelper(helper);
4297
+ } finally {
4298
+ // The helper normally removes its app-server shim before exiting. On
4299
+ // Windows, a descendant may still hold the copied codex.exe briefly
4300
+ // after the helper closes, so retry the parent-owned cleanup once the
4301
+ // helper shutdown path has completed as well.
4302
+ if (helperShimDir) {
4303
+ try {
4304
+ removeDirectoryWithRetry(helperShimDir);
4305
+ } catch {
4306
+ // Best-effort cleanup; stale helper directories are swept on the
4307
+ // next runtime app launch.
4308
+ }
4309
+ }
4310
+ }
4167
4311
  };
4168
4312
 
4169
4313
  return {
@@ -5166,7 +5310,7 @@ async function main() {
5166
5310
 
5167
5311
  const rawArgs = process.argv.slice(2);
5168
5312
  if (rawArgs[0] === INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG) {
5169
- return runRuntimeRotationAppHelper();
5313
+ return runRuntimeRotationAppHelper(rawArgs[1] ?? "");
5170
5314
  }
5171
5315
 
5172
5316
  const normalizedArgs = normalizeAuthAlias(rawArgs);
@@ -6,6 +6,8 @@ import { join } from "node:path";
6
6
  import process from "node:process";
7
7
 
8
8
  const PLUGIN_NAME = "codex-multi-auth";
9
+ const LEGACY_PLUGIN_NAME = "@ndycode/codex-multi-auth";
10
+ const PLUGIN_NAMES = [PLUGIN_NAME, LEGACY_PLUGIN_NAME];
9
11
 
10
12
  const TRUE_VALUES = new Set(["1", "true", "yes"]);
11
13
  const FALSE_VALUES = new Set(["0", "false", "no"]);
@@ -82,6 +84,7 @@ export function resolveInstallPaths(
82
84
  configPath,
83
85
  cacheDir,
84
86
  cacheNodeModules: join(cacheDir, "node_modules", PLUGIN_NAME),
87
+ cacheLegacyNodeModules: join(cacheDir, "node_modules", "@ndycode", "codex-multi-auth"),
85
88
  cacheBunLock: join(cacheDir, "bun.lock"),
86
89
  cachePackageJson: join(cacheDir, "package.json"),
87
90
  };
@@ -92,7 +95,9 @@ export function removePluginFromList(list) {
92
95
  const entries = Array.isArray(list) ? list.filter(Boolean) : [];
93
96
  return entries.filter((entry) => {
94
97
  if (typeof entry !== "string") return true;
95
- return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
98
+ return !PLUGIN_NAMES.some(
99
+ (pluginName) => entry === pluginName || entry.startsWith(`${pluginName}@`),
100
+ );
96
101
  });
97
102
  }
98
103
 
@@ -101,7 +106,9 @@ export function normalizePluginList(list) {
101
106
  const entries = Array.isArray(list) ? list.filter(Boolean) : [];
102
107
  const filtered = entries.filter((entry) => {
103
108
  if (typeof entry !== "string") return true;
104
- return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
109
+ return !PLUGIN_NAMES.some(
110
+ (pluginName) => entry === pluginName || entry.startsWith(`${pluginName}@`),
111
+ );
105
112
  });
106
113
  const deduped = [];
107
114
  const seen = new Set();
@@ -33,6 +33,8 @@ const CI_ENV_KEYS = [
33
33
  export const INSTALL_NOTICE = [
34
34
  "codex-multi-auth installed. Run `codex-multi-auth --help` to get started.",
35
35
  "App integration (Codex app bind + launcher shortcuts) completes automatically on first run.",
36
+ "To remove it later, run `codex-multi-auth uninstall`, then `npm uninstall -g codex-multi-auth`.",
37
+ "`@ndycode/codex-multi-auth` is the legacy scoped package name; it does not remove this package.",
36
38
  ].join("\n");
37
39
 
38
40
  /**
@@ -189,6 +189,9 @@ export async function runPreuninstallCleanup(deps = {}) {
189
189
  );
190
190
  if (dryRun) {
191
191
  log(`[dry-run] Would remove ${paths.cacheNodeModules}`);
192
+ if (paths.cacheLegacyNodeModules) {
193
+ log(`[dry-run] Would remove ${paths.cacheLegacyNodeModules}`);
194
+ }
192
195
  if (bunLockSafe) {
193
196
  log(`[dry-run] Would remove ${paths.cacheBunLock}`);
194
197
  } else {
@@ -201,6 +204,14 @@ export async function runPreuninstallCleanup(deps = {}) {
201
204
  await withFileOperationRetry(() =>
202
205
  rm(paths.cacheNodeModules, { recursive: true, force: true }),
203
206
  );
207
+ if (paths.cacheLegacyNodeModules) {
208
+ await withFileOperationRetry(() =>
209
+ rm(paths.cacheLegacyNodeModules, {
210
+ recursive: true,
211
+ force: true,
212
+ }),
213
+ );
214
+ }
204
215
  if (bunLockSafe) {
205
216
  await withFileOperationRetry(() =>
206
217
  rm(paths.cacheBunLock, { force: true }),