type RmLike = (path: string, opts: { recursive?: boolean; force?: boolean }) => Promise; function isRetryableRmError(err: unknown): boolean { if (!err || typeof err !== 'object') return false; const code = (err as { code?: unknown }).code; return code === 'EBUSY' || code === 'EPERM' || code === 'EACCES'; } export async function rmWithRetries( rm: RmLike, targetPath: string, opts: { recursive?: boolean; force?: boolean }, retries = 6, ): Promise { let attempt = 0; // Windows: file locks/antivirus/indexers can cause transient EPERM/EBUSY. // We retry a few times before surfacing the error. // Delays: 20, 40, 80, 160, 320, 640ms (capped by retries). for (;;) { try { await rm(targetPath, opts); return; } catch (e) { attempt += 1; if (attempt > retries || !isRetryableRmError(e)) { throw e; } const delayMs = Math.min(800, 20 * 2 ** (attempt - 1)); await new Promise((r) => setTimeout(r, delayMs)); } } }