laurent22/joplin · error · Error

Cannot find "%s".

Error message

Cannot find "%s".

What it means

Thrown by the `tag` command when `tag-command === 'remove'` and the tag specified by `args.tag` could not be loaded (the earlier `app().loadItem(ModelType.Tag, ...)` returned null). The check runs before the add/remove branches so that a non-existent tag aborts removal early. `%s` is replaced with the requested tag title.

Source

Thrown at packages/app-cli/app/command-tag.ts:37

		return [['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
	}

	public override async action(args: { 'tag-command': string; tag?: string; note?: string; options: { long?: boolean } }) {
		let tag: TagEntity | null = null;
		const options = args.options;

		// app.loadItem's parameter type is narrower (Note | Folder | 'folderOrNote') than the
		// runtime support, which falls through to BaseItem.itemClass(type).loadByTitle for other
		// types. ModelType.Tag is one of those — cast to satisfy the type checker.
		if (args.tag) tag = await app().loadItem(ModelType.Tag as ModelType.Note, args.tag);
		let notes: NoteEntity[] = [];
		if (args.note) {
			notes = await app().loadItems(ModelType.Note, args.note);
		}

		const command = args['tag-command'];

		if (command === 'remove' && !tag) throw new Error(_('Cannot find "%s".', args.tag));

		if (command === 'add') {
			if (!notes.length) throw new Error(_('Cannot find "%s".', args.note));
			if (!tag) tag = await Tag.save({ title: args.tag }, { userSideValidation: true });
			for (let i = 0; i < notes.length; i++) {
				await Tag.addNote(tag.id, notes[i].id);
			}
		} else if (command === 'remove') {
			if (!tag) throw new Error(_('Cannot find "%s".', args.tag));
			if (!notes.length) throw new Error(_('Cannot find "%s".', args.note));
			for (let i = 0; i < notes.length; i++) {
				await Tag.removeNote(tag.id, notes[i].id);
			}
		} else if (command === 'list') {
			if (tag) {
				const notes: NoteEntity[] = await Tag.notes(tag.id);
				notes.map(note => {
					let line = '';

View on GitHub (pinned to 2654b33620)

Solutions

  1. List tags with `:tag list` to confirm the exact title and retry.
  2. Sync first if the tag may exist only on another device.
  3. If you only want to ensure the tag is gone, the error confirms it is — no further action needed.

Example fix

// before
if (command === 'remove' && !tag) throw new Error(_('Cannot find "%s".', args.tag));

// after - hint at available tags
if (command === 'remove' && !tag) throw new Error(_('Cannot find tag "%s". Run \`tag list\` to see existing tags.', args.tag));
Defensive patterns

Strategy: validation

Validate before calling

const tag = await app().loadItem(ModelType.Tag as ModelType.Note, tagName);
if (subcommand === 'remove' && !tag) {
	throw new Error(`Tag "${tagName}" not found. Run \`tag list\`.`);
}

Type guard

const isExistingTag = (t: TagEntity | null): t is TagEntity => t !== null;

Try / catch

try {
	await app().executeCommand(['tag', 'remove', tagName, noteTitle]);
} catch (e) {
	if (/Cannot find/.test(e.message) && /* tag context */) { /* re-list tags */ }
	else throw e;
}

Prevention

When it happens

Trigger: Running `:tag remove <tag> [<note>]` where `<tag>` does not match any Tag.title in the database. Because removal requires an existing tag, a missing tag is a hard error rather than a no-op.

Common situations: Typo in the tag name; tag was renamed or deleted; referencing a tag from another profile; tags not yet synced from the server.

Related errors


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