laurent22/joplin · error · Error

Cannot find "%s".

Error message

Cannot find "%s".

What it means

Thrown by the `search` CLI command when the optional `--notebook` argument names a folder that Folder.loadByTitle cannot resolve in the local database. The command uses the title (not id) to look up the folder that should scope the saved search, so a non-existent or mistyped notebook title aborts before the SEARCH_ADD dispatch. The %s placeholder is replaced with the offending title for display.

Source

Thrown at packages/app-cli/app/command-search.ts:28

	}

	public override description() {
		return _('Searches for the given <pattern> in all the notes.');
	}

	public override compatibleUis() {
		return ['gui'];
	}

	// eslint-disable-next-line id-denylist -- `notebook` is the CLI argument name declared in usage() and accessed via bracket notation; the identifier appears here only as a type property key
	public override async action(args: { pattern: string; notebook?: string }) {
		const pattern = args['pattern'];
		const folderTitle = args['notebook'];

		let folder = null;
		if (folderTitle) {
			folder = await Folder.loadByTitle(folderTitle);
			if (!folder) throw new Error(_('Cannot find "%s".', folderTitle));
		}

		const searchId = uuid.create();

		this.dispatch({
			type: 'SEARCH_ADD',
			search: {
				id: searchId,
				title: pattern,
				query_pattern: pattern,
				query_folder_id: folder ? folder.id : '',
				type_: BaseModel.TYPE_SEARCH,
			},
		});

		this.dispatch({
			type: 'SEARCH_SELECT',
			id: searchId,

View on GitHub (pinned to 2654b33620)

Solutions

  1. Run `ls notebook` / `:notebook list` to confirm the exact stored title and re-run with the correct spelling and case.
  2. If the notebook lives on another device, run `:sync` first so the folder is replicated into the local database, then retry the search.
  3. If the title contains spaces or special characters, quote it exactly as shown by the notebook list command.
  4. Omit `--notebook` entirely to create an unscoped (global) saved search instead.

Example fix

// before
folder = await Folder.loadByTitle(folderTitle);
if (!folder) throw new Error(_('Cannot find "%s".', folderTitle));

// after - hint the user with available titles before failing
const candidates = await Folder.all();
const folder = candidates.find(f => f.title === folderTitle);
if (!folder) throw new Error(_('Cannot find "%s". Known notebooks: %s', folderTitle, candidates.map(f => f.title).join(', ')));
Defensive patterns

Strategy: validation

Validate before calling

// Before calling the command, resolve the folder title yourself and show a helpful error.
const folder = await Folder.loadByTitle(notebookTitle);
if (!folder) {
	throw new Error(`Notebook "${notebookTitle}" not found. Run \`ls notebook\` first.`);
}

Type guard

const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;

Try / catch

try {
	await app().executeCommand(['search', pattern, '--notebook', notebookTitle]);
} catch (e) {
	if (/Cannot find/.test(e.message)) { /* prompt user for correct notebook title */ }
	else throw e;
}

Prevention

When it happens

Trigger: Running `:search <pattern> --notebook <title>` (or the equivalent GUI invocation routed through this command) where `<title>` does not match any Folder.title in the current profile's database. Also triggered if the folder exists only on a sync target that has not yet been pulled locally, or when the title contains whitespace/casing that differs from the stored value.

Common situations: Typos in the notebook name; referencing a notebook created on another device before the first sync completes; duplicate or renamed notebooks where the user recalls the old title; running the command against a fresh/empty profile.

Related errors


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