laurent22/joplin · error · Error

Cannot find unique filename

Error message

Cannot find unique filename

What it means

Thrown by findUniqueFilename() in the base fs driver. It appends a counter (or, after 1000 tries, a timestamp) to generate a non-existent, non-reserved name. If after 1100 attempts every candidate still exists or is reserved, it gives up rather than loop infinitely.

Source

Thrown at packages/lib/fs-driver-base.ts:227

		const nameNoExt = pathPrefix + filename(baseName);
		let extension = fileExtension(baseName);
		if (extension) extension = `.${extension}`;
		let nameToTry = nameNoExt + extension;
		while (true) {
			// Check if the filename does not exist in the filesystem and is not reserved
			const exists = await this.exists(nameToTry) || isReserved(nameToTry);
			if (!exists) return nameToTry;
			if (!markdownSafe) {
				nameToTry = `${nameNoExt} (${counter})${extension}`;
			} else {
				nameToTry = `${nameNoExt}-${counter}${extension}`;
			}
			counter++;
			if (counter >= 1000) {
				nameToTry = `${nameNoExt} (${new Date().getTime()})${extension}`;
				await time.msleep(10);
			}
			if (counter >= 1100) throw new Error('Cannot find unique filename');
		}
	}

	public async removeAllThatStartWith(dirPath: string, filenameStart: string) {
		if (!filenameStart || !dirPath) throw new Error('dirPath and filenameStart cannot be empty');

		const stats = await this.readDirStats(dirPath);

		for (const stat of stats) {
			if (stat.path.indexOf(filenameStart) === 0) {
				await this.remove(`${dirPath}/${stat.path}`);
			}
		}
	}

	public async waitTillExists(path: string, timeout = 10000) {
		const startTime = Date.now();

View on GitHub (pinned to 2654b33620)

Solutions

  1. Clear or archive the target directory so existing-name collisions drop below 1000.
  2. Use a more distinctive base name (include a date or random component) before calling findUniqueFilename.
  3. Review and trim the reservedNames list if it's catching legitimate candidates.
  4. If you control the caller, fall back to a UUID-based name instead of the counter loop.

Example fix

// before
const name = await shim.fsDriver().findUniqueFilename(dir, baseName);
// after - prepend a unique stamp to avoid the 1100-try ceiling
const stamped = `${baseName}-${Date.now()}`;
const name = await shim.fsDriver().findUniqueFilename(dir, stamped);
Defensive patterns

Strategy: validation

Validate before calling

// Cap how many colliding names exist in the target dir before generating.
const colliding = (await shim.fsDriver().readDirStats(dir)).filter(s => s.path.indexOf(baseName) === 0).length;
if (colliding > 900) throw new Error(`Too many name collisions for ${baseName}; clear the directory.`);

Type guard

function isUniqueFilenameExhausted(e: any): boolean {
  return e && typeof e.message === 'string' && e.message === 'Cannot find unique filename';
}

Try / catch

let name;
try {
  name = await shim.fsDriver().findUniqueFilename(dir, baseName);
} catch (e) {
  if (isUniqueFilenameExhausted(e)) {
    name = `${dir}/${baseName}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling findUniqueFilename for a directory containing thousands of files with the same base name and incrementing pattern, or with many reserved names colliding with the generated candidates.

Common situations: Exporting/importing thousands of notes with identical titles; a directory polluted with stale 'name (N).md' files from prior exports; reserved-name list overly broad and colliding; pathological filenames; a bug creating files faster than uniqueness can be found.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/0653435174745810. Report an issue: GitHub.