laurent22/joplin · error · Error

[user] is required

Error message

[user] is required

What it means

Thrown by the `share` command dispatcher after a notebook was resolved, when the subcommand is `add` or `remove` and no `[user]` argument was supplied. Both subcommands operate on a recipient identified by email, so the user argument is mandatory for them (but NOT for `delete`, which is why the guard sits in the else-branch).

Source

Thrown at packages/app-cli/app/command-share.ts:261

			const ok = force ? true : await this.prompt(
				_('Unshare notebook "%s"? This may cause other users to lose access to the notebook.', folderTitle(folder)),
				{ booleanAnswerDefault: 'n' },
			);
			if (!ok) return;

			logger.info('Unsharing folder', folder.id);
			await ShareService.instance().unshareFolder(folder.id);
			await reg.waitForSyncFinishedThenSync();
		};

		if (args.command === 'add' || args.command === 'remove' || args.command === 'delete') {
			if (!args.notebook) throw new Error('[notebook] is required');
			const folder = await app().loadItemOrFail(ModelType.Folder, args.notebook);

			if (args.command === 'delete') {
				return commandShareDelete(folder);
			} else {
				if (!args.user) throw new Error('[user] is required');

				const email = args.user;
				if (args.command === 'add') {
					return commandShareAdd(folder, email);
				} else if (args.command === 'remove') {
					return commandShareRemove(folder, email);
				}
			}
		}

		if (args.command === 'leave') {
			const folder = args.notebook ? await app().loadItemOrFail(ModelType.Folder, args.notebook) : null;

			await ShareService.instance().maintenance();

			return CommandService.instance().execute(
				'leaveSharedFolder', folder?.id, { force: args.options.force },
			);

View on GitHub (pinned to 2654b33620)

Solutions

  1. Re-run with the recipient email: `:share add <notebook> <email>` / `:share remove <notebook> <email>`.
  2. If you want to unshare entirely (all recipients at once), use `:share delete <notebook>` instead.
  3. When scripting, assert the email variable is set before invoking.

Example fix

// before
if (!args.user) throw new Error('[user] is required');

// after - clarify the requirement and the delete alternative
if (!args.user) throw new Error('[user] email is required for add/remove. Use \`share delete <notebook>\` to remove all recipients.');
Defensive patterns

Strategy: validation

Validate before calling

if (['add','remove'].includes(command) && !args.user) {
	throw new Error('[user] email is required for add/remove. Use \`share delete\` to remove all recipients.');
}

Type guard

const isSubcommandRequiringUser = (c: string): boolean => ['add','remove'].includes(c);

Try / catch

try {
	await app().executeCommand(['share', 'add', notebook, email]);
} catch (e) {
	if (/\[user\] is required/.test(e.message)) { /* reprompt for email */ }
	else throw e;
}

Prevention

When it happens

Trigger: Running `:share add <notebook>` or `:share remove <notebook>` without the trailing email/user argument. The guard fires only after the notebook loads successfully, so a bad notebook errors earlier at error 48's path.

Common situations: Forgot the email argument; assumed `remove` with no user removes all recipients (it does not); scripting with an unset email variable.

Related errors


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