stablyai/orca · error · Error

Could not choose a unique file name in Downloads.

Error message

Could not choose a unique file name in Downloads.

What it means

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.

Source

Thrown at src/main/browser/browser-download-destination.ts:86

    const safeFilename = normalizeFilename(filename)
    const downloadsPath = this.downloadsPath()

    for (let attempt = 0; attempt < MAX_BROWSER_DOWNLOAD_COLLISION_ATTEMPTS; attempt += 1) {
      const candidateFilename = buildCollisionCandidate(safeFilename, attempt)
      const savePath = path.join(downloadsPath, candidateFilename)
      const reservationKey = normalizeReservationKey(savePath, this.platform)
      if (this.reservedPathKeys.has(reservationKey) || this.pathExists(savePath)) {
        continue
      }
      this.reservedPathKeys.add(reservationKey)
      return {
        filename: candidateFilename,
        savePath,
        reservationKey
      }
    }

    throw new Error('Could not choose a unique file name in Downloads.')
  }

  release(reservationKey: string | null): void {
    if (!reservationKey) {
      return
    }
    this.reservedPathKeys.delete(reservationKey)
  }

  clear(): void {
    this.reservedPathKeys.clear()
  }
}

export const browserDownloadDestinationReservations = new BrowserDownloadDestinationReservations()

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Clean up the Downloads directory — remove or archive old collision-suffixed files so candidates are free.
  2. Ensure release(reservationKey) is called when each download completes or is cancelled, so reservedPathKeys doesn't grow unboundedly.
  3. If this is a reservation leak, audit the download lifecycle (WillDownload → completed/cancelled) to confirm release() is always invoked.
  4. 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).
  5. If legitimately downloading many same-named files, pre-rename the source or destination to avoid relying on collision suffixing beyond 1000.

Example fix

// before — reservations leak if download is cancelled without release
const dest = reservations.reserve('report.pdf')
// ... download starts, user cancels, release() never called

// after — always release in a finally block
const dest = reservations.reserve('report.pdf')
try {
  await performDownload(dest.savePath)
} finally {
  reservations.release(dest.reservationKey)
}
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'node:fs'
import path from 'node:path'
function canReserve(filename: string, downloadsPath: string, reserved: Set<string>): boolean {
  for (let i = 0; i < 1000; i++) {
    const candidate = i === 0 ? filename : `${path.parse(filename).name} (${i})${path.extname(filename)}`
    const p = path.join(downloadsPath, candidate)
    if (!reserved.has(p.toLowerCase()) && !existsSync(p)) return true
  }
  return false
}

Try / catch

try {
  return reservations.reserve(filename)
} catch (e) {
  if (e instanceof Error && /unique file name/i.test(e.message)) {
    reservations.clear() // drop stale reservations and retry
    return reservations.reserve(filename)
  }
  throw e
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/d1d6edabc8ace4a9. Report an issue: GitHub.