laurent22/joplin · error · Error

Ambiguous notebook "%s". Please use short notebook id instea

Error message

Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id

What it means

Thrown by `mv <item> <notebook>` when the destination was found by loadItem but `Folder.search({ titlePattern: destination, limit: 2 })` returns >1 row. Same LIKE-matching ambiguity as mkbook's validDestinationFolder: the destination string is a substring of multiple notebook titles, so the move target is ambiguous even though an exact match exists.

Source

Thrown at packages/app-cli/app/command-mv.ts:29

	}

	public override description() {
		return _('Moves the given <item> to [notebook]');
	}

	public override async action(args: { item: string; 'notebook': string }) {
		const pattern = args['item'];
		const destination = args['notebook'];
		let folder = null;

		if (destination !== 'root') {
			folder = await app().loadItem(ModelType.Folder, destination);
			if (!folder) throw new Error(_('Cannot find "%s".', destination));
		}

		const destinationDuplicates = await Folder.search({ titlePattern: destination, limit: 2 });
		if (destinationDuplicates.length > 1) {
			throw new Error(_('Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id', destination));
		}

		const itemFolder = await app().loadItem(ModelType.Folder, pattern);
		if (itemFolder) {
			const sourceDuplicates = await Folder.search({ titlePattern: pattern, limit: 2 });
			if (sourceDuplicates.length > 1) {
				throw new Error(_('Ambiguous notebook "%s". Please use notebook id instead - press "ti" to see the short notebook id or use $b for current selected notebook', pattern));
			}
			if (destination === 'root') {
				await Folder.moveToFolder(itemFolder.id, '');
			} else {
				await Folder.moveToFolder(itemFolder.id, folder.id);
			}
		} else {
			const notes = await app().loadItems(ModelType.Note, pattern);
			if (notes.length === 0) throw new Error(_('Cannot find "%s".', pattern));
			for (let i = 0; i < notes.length; i++) {
				await Note.moveToFolder(notes[i].id, folder.id);

View on GitHub (pinned to 2654b33620)

Solutions

  1. Use the short notebook id: `joplin mv <item> <short-id>`
  2. Rename colliding notebooks to disambiguate
  3. Use the full 32-char folder id

Example fix

// before
joplin mv mynote Note   // Note LIKE-matches "Notes" + "My Notes" -> throws

// after
joplin mv mynote 7f3a    // short id
Defensive patterns

Strategy: validation

Validate before calling

import Folder from '@joplin/lib/models/Folder';

async function assertDestinationUnambiguous(destRef: string) {
  const dups = await Folder.search({ titlePattern: destRef, limit: 2 });
  if (dups.length > 1) {
    throw new Error(`Destination "${destRef}" is ambiguous. Use the short notebook id.`);
  }
}

Type guard

const isUnambiguous = async (ref: string): Promise<boolean> =>
  (await Folder.search({ titlePattern: ref, limit: 2 })).length <= 1;

Prevention

When it happens

Trigger: Destination name LIKE-matches several notebooks (e.g., `mv Note X` when "Notes" and "My Notes" both exist); short destination strings; overlapping names in a large catalog.

Common situations: Notebook tree with similarly-named folders; abbreviated destination references in batch scripts.

Related errors


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