{"record":{"id":"d1d6edabc8ace4a9","repo":"stablyai/orca","slug":"could-not-choose-a-unique-file-name-in-downloads","errorCode":null,"errorMessage":"Could not choose a unique file name in Downloads.","messagePattern":"Could not choose a unique file name in Downloads\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/browser/browser-download-destination.ts","lineNumber":86,"sourceCode":"    const safeFilename = normalizeFilename(filename)\n    const downloadsPath = this.downloadsPath()\n\n    for (let attempt = 0; attempt < MAX_BROWSER_DOWNLOAD_COLLISION_ATTEMPTS; attempt += 1) {\n      const candidateFilename = buildCollisionCandidate(safeFilename, attempt)\n      const savePath = path.join(downloadsPath, candidateFilename)\n      const reservationKey = normalizeReservationKey(savePath, this.platform)\n      if (this.reservedPathKeys.has(reservationKey) || this.pathExists(savePath)) {\n        continue\n      }\n      this.reservedPathKeys.add(reservationKey)\n      return {\n        filename: candidateFilename,\n        savePath,\n        reservationKey\n      }\n    }\n\n    throw new Error('Could not choose a unique file name in Downloads.')\n  }\n\n  release(reservationKey: string | null): void {\n    if (!reservationKey) {\n      return\n    }\n    this.reservedPathKeys.delete(reservationKey)\n  }\n\n  clear(): void {\n    this.reservedPathKeys.clear()\n  }\n}\n\nexport const browserDownloadDestinationReservations = new BrowserDownloadDestinationReservations()\n","sourceCodeStart":68,"sourceCodeEnd":102,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/browser/browser-download-destination.ts#L68-L102","documentation":"Thrown by BrowserDownloadDestinationReservations.reserve() after exhausting MAX_BROWSER_DOWNLOAD_COLLISION_ATTEMPTS (1000) candidates without finding a unique filename. For each attempt it builds a candidate (filename, filename (1), filename (2), ...) and checks if the reservation key is already held OR if the path exists on disk. If all 1000 are taken, it gives up. This is a plain Error (not BrowserError) indicating the Downloads directory is saturated with identically-named files.","triggerScenarios":"reserve(filename) loops attempt 0..999; each candidate's savePath either already exists on disk (pathExists) or its reservationKey is in reservedPathKeys (another in-flight download claimed it). If 1000 consecutive candidates collide, the loop exits and throws. Happens when the Downloads dir already has 'file.txt' through 'file (999).txt' OR when reservedPathKeys holds 1000 keys for the same stem (1000 concurrent downloads of the same filename).","commonSituations":"A Downloads directory with hundreds of identically-named downloaded files accumulated over time (no cleanup); a burst of concurrent downloads all named the same (e.g. programmatic download of 'report.pdf' x1000); a bug where reservations are never released (release() not called on completion/cancel) causing the reserved set to grow unboundedly; a pathExists mock in tests that always returns true.","solutions":["Clean up the Downloads directory — remove or archive old collision-suffixed files so candidates are free.","Ensure release(reservationKey) is called when each download completes or is cancelled, so reservedPathKeys doesn't grow unboundedly.","If this is a reservation leak, audit the download lifecycle (WillDownload → completed/cancelled) to confirm release() is always invoked.","Call clear() on the reservations instance if the reserved set is known to be stale (e.g. after app restart where in-flight downloads didn't persist).","If legitimately downloading many same-named files, pre-rename the source or destination to avoid relying on collision suffixing beyond 1000."],"exampleFix":"// before — reservations leak if download is cancelled without release\nconst dest = reservations.reserve('report.pdf')\n// ... download starts, user cancels, release() never called\n\n// after — always release in a finally block\nconst dest = reservations.reserve('report.pdf')\ntry {\n  await performDownload(dest.savePath)\n} finally {\n  reservations.release(dest.reservationKey)\n}","handlingStrategy":"validation","validationCode":"import { existsSync } from 'node:fs'\nimport path from 'node:path'\nfunction canReserve(filename: string, downloadsPath: string, reserved: Set<string>): boolean {\n  for (let i = 0; i < 1000; i++) {\n    const candidate = i === 0 ? filename : `${path.parse(filename).name} (${i})${path.extname(filename)}`\n    const p = path.join(downloadsPath, candidate)\n    if (!reserved.has(p.toLowerCase()) && !existsSync(p)) return true\n  }\n  return false\n}","typeGuard":null,"tryCatchPattern":"try {\n  return reservations.reserve(filename)\n} catch (e) {\n  if (e instanceof Error && /unique file name/i.test(e.message)) {\n    reservations.clear() // drop stale reservations and retry\n    return reservations.reserve(filename)\n  }\n  throw e\n}","preventionTips":["Always call release(reservationKey) in a finally block when a download completes or cancels","Periodically clean the Downloads directory of old collision-suffixed files","Call clear() on app startup to drop stale reservations from a previous session","Audit the download lifecycle to ensure release() is invoked on both success and cancellation paths"],"tags":["downloads","filesystem","collision","reservations"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}