()
| 1182 | } |
| 1183 | |
| 1184 | export async function cleanupOldVersions(): Promise<void> { |
| 1185 | // Yield to ensure we don't block startup |
| 1186 | await Promise.resolve() |
| 1187 | |
| 1188 | const dirs = getBaseDirectories() |
| 1189 | const oneHourAgo = Date.now() - 3600000 |
| 1190 | |
| 1191 | // Clean up old renamed executables on Windows (no longer running at startup) |
| 1192 | if (getPlatform().startsWith('win32')) { |
| 1193 | const executableDir = dirname(dirs.executable) |
| 1194 | try { |
| 1195 | const files = await readdir(executableDir) |
| 1196 | let cleanedCount = 0 |
| 1197 | for (const file of files) { |
| 1198 | if (!/^claude\.exe\.old\.\d+$/.test(file)) continue |
| 1199 | try { |
| 1200 | await unlink(join(executableDir, file)) |
| 1201 | cleanedCount++ |
| 1202 | } catch { |
| 1203 | // File might still be in use by another process |
| 1204 | } |
| 1205 | } |
| 1206 | if (cleanedCount > 0) { |
| 1207 | logForDebugging( |
| 1208 | `Cleaned up ${cleanedCount} old Windows executables on startup`, |
| 1209 | ) |
| 1210 | } |
| 1211 | } catch (error) { |
| 1212 | if (!isENOENT(error)) { |
| 1213 | logForDebugging(`Failed to clean up old Windows executables: ${error}`) |
| 1214 | } |
| 1215 | } |
| 1216 | } |
| 1217 | |
| 1218 | // Clean up orphaned staging directories older than 1 hour |
| 1219 | try { |
| 1220 | const stagingEntries = await readdir(dirs.staging) |
| 1221 | let stagingCleanedCount = 0 |
| 1222 | for (const entry of stagingEntries) { |
| 1223 | const stagingPath = join(dirs.staging, entry) |
| 1224 | try { |
| 1225 | // stat() is load-bearing here (we need mtime). There is a theoretical |
| 1226 | // TOCTOU where a concurrent installer could freshen a stale staging |
| 1227 | // dir between stat and rm — but the 1-hour threshold makes this |
| 1228 | // vanishingly unlikely, and rm({force:true}) tolerates concurrent |
| 1229 | // deletion. |
| 1230 | const stats = await stat(stagingPath) |
| 1231 | if (stats.mtime.getTime() < oneHourAgo) { |
| 1232 | await rm(stagingPath, { recursive: true, force: true }) |
| 1233 | stagingCleanedCount++ |
| 1234 | logForDebugging(`Cleaned up old staging directory: ${entry}`) |
| 1235 | } |
| 1236 | } catch { |
| 1237 | // Ignore individual errors |
| 1238 | } |
| 1239 | } |
| 1240 | if (stagingCleanedCount > 0) { |
| 1241 | logForDebugging( |
no test coverage detected