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
- Verify candidates with `joplin ls` and copy exact titles or ids
- Switch to the right notebook first: `joplin use <notebook>`
- Use a glob to broaden: `joplin rmnote 'Meeting*'`
- 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
- Resolve note ids with `ls` before destructive `rmnote` calls
- Use globs (`Pattern*`) for broader matches, and `-f` only after confirming the match set
- Remember note lookups (non-glob) are scoped to currentFolder
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.