laurent22/joplin · error · Error

Cannot find "%s".

Error message

Cannot find "%s".

What it means

Thrown by the `todo` CLI command when the note-pattern argument does not match any note in the database. The command calls app().loadItems(ModelType.Note, pattern) and checks that the returned array is non-empty before proceeding; an empty result means no note matched the glob/regex pattern supplied on the command line.

Source

Thrown at packages/app-cli/app/command-todo.ts:22

import { ModelType } from '@joplin/lib/BaseModel';
import Note from '@joplin/lib/models/Note';
import time from '@joplin/lib/time';
import { NoteEntity } from '@joplin/lib/services/database/types';

class Command extends BaseCommand {
	public override usage() {
		return 'todo <todo-command> <note-pattern>';
	}

	public override description() {
		return _('<todo-command> can either be "toggle" or "clear". Use "toggle" to toggle the given to-do between completed and uncompleted state (If the target is a regular note it will be converted to a to-do). Use "clear" to convert the to-do back to a regular note.');
	}

	public override async action(args: { 'todo-command': string; 'note-pattern': string }) {
		const action = args['todo-command'];
		const pattern = args['note-pattern'];
		const notes: NoteEntity[] = await app().loadItems(ModelType.Note, pattern);
		if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));

		for (let i = 0; i < notes.length; i++) {
			const note = notes[i];

			this.encryptionCheck(note);

			let toSave: NoteEntity = {
				id: note.id,
			};

			if (action === 'toggle') {
				if (!note.is_todo) {
					toSave = Note.toggleIsTodo(note);
				} else {
					toSave.todo_completed = note.todo_completed ? 0 : time.unixMs();
				}
			} else if (action === 'clear') {
				toSave.is_todo = 0;

View on GitHub (pinned to 2654b33620)

Solutions

  1. Run `ls` (the note-list CLI command) with the same pattern to confirm what matches, then adjust the pattern.
  2. Verify the note title spelling and case against the database.
  3. Try the note's ID instead of a title substring as the pattern.
  4. Ensure the CLI is pointed at the correct profile/database (JOPLIN_PROFILE / --profile).

Example fix

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

// after — surface the count and hint
const notes = await app().loadItems(ModelType.Note, pattern);
if (!notes.length) throw new Error(_('Cannot find "%s". Run `ls <pattern>` to list matching notes.', pattern));
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking the todo command, confirm the pattern matches.
const notes = await app().loadItems(ModelType.Note, pattern);
if (notes.length === 0) {
  console.error(`No notes match "${pattern}". Aborting.`);
  return;
}
// safe to proceed with toggle/clear

Type guard

function patternHasMatches(notes: NoteEntity[]): boolean {
  return Array.isArray(notes) && notes.length > 0;
}

Try / catch

try {
  await command.exec(['todo', 'toggle', pattern]);
} catch (e) {
  if (e.message.startsWith('Cannot find')) {
    // pattern matched nothing — surface to user, do not retry blindly
  } else throw e;
}

Prevention

When it happens

Trigger: Running `todo toggle <pattern>` or `todo clear <pattern>` where <pattern> matches zero notes. The match is delegated to app().loadItems which applies the pattern against note titles/IDs; any pattern yielding no hits triggers this.

Common situations: Typo in the note pattern, referencing a note by an old title after a rename, the note existing in a different notebook/sync target, or quoting a pattern with shell special characters that get mangled.

Related errors


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