laurent22/joplin · error · Error

Cannot find "%s".

Error message

Cannot find "%s".

What it means

Thrown by `rmnote <note-pattern>` when `app().loadItems(ModelType.Note, pattern)` returns an empty array. The pattern supports exact title, full id, partial id (>=2 chars), and glob (`*`) when the pattern contains `*` (app.ts:101-104, scoped to currentFolder). After the length check, the command prompts for confirmation (skippable with `-f`) and may force permanent deletion with `-p` or when all matched notes already have `deleted_time`.

Source

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

	}

	public override description() {
		return _('Deletes the notes matching <note-pattern>.');
	}

	public override options() {
		return [
			['-f, --force', _('Deletes the notes without asking for confirmation.')],
			['-p, --permanent', _('Deletes notes permanently, skipping the trash.')],
		];
	}

	public override async action(args: { 'note-pattern': string; options?: { force?: boolean; permanent?: boolean } }) {
		const pattern = args['note-pattern'];
		const force = args.options && args.options.force === true;

		const notes: NoteEntity[] = await app().loadItems(ModelType.Note, pattern);
		if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));

		let ok = true;
		if (!force && notes.length > 1) {
			ok = await this.prompt(_n('%d note matches this pattern. Delete it?', '%d notes match this pattern. Delete them?', notes.length, notes.length), { booleanAnswerDefault: 'n' });
		}

		const permanent = (args.options?.permanent === true) || notes.every(n => !!n.deleted_time);
		if (!force && permanent) {
			const message = (
				_n('%d note will be permanently deleted. Continue?', '%d notes will be permanently deleted. Continue?', notes.length, notes.length)
			);
			ok = await this.prompt(message, { booleanAnswerDefault: 'n' });
		}

		if (!ok) return;

		const ids = notes.map(n => n.id);
		const options: DeleteOptions = {

View on GitHub (pinned to 2654b33620)

Solutions

  1. Verify candidates with `joplin ls` and copy exact titles or ids
  2. Switch to the right notebook first: `joplin use <notebook>`
  3. Use a glob to broaden: `joplin rmnote 'Meeting*'`
  4. Use `-f` to skip the confirmation prompt once you've confirmed matches exist

Example fix

// before
joplin use WrongBook
joplin rmnote "Draft"   // throws: Cannot find "Draft".

// after
joplin use Work
joplin ls                       # confirm "Draft" is listed
joplin rmnote Draft
# or by id, or glob:
joplin rmnote 'Draf*'
Defensive patterns

Strategy: validation

Validate before calling

import app from './app';
import { ModelType } from '@joplin/lib/BaseModel';

async function resolveNotesForDelete(pattern: string) {
  const notes = await app().loadItems(ModelType.Note, pattern);
  if (!notes.length) throw new Error(`Cannot find "${pattern}". Verify with \`ls\`, use a glob, or switch notebooks.`);
  return notes;
}

Type guard

const hasDeleteCandidates = (n: any[]): n is { id: string }[] =>
  Array.isArray(n) && n.length > 0;

Prevention

When it happens

Trigger: Typo in the note pattern; note doesn't exist in the current notebook (non-glob lookups are parent-scoped to currentFolder); partial id <2 chars; note already permanently deleted.

Common situations: Bulk-delete scripts with stale titles; running rmnote from the wrong notebook; glob patterns that match nothing.

Related errors


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