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
- Re-run with the recipient email: `:share add <notebook> <email>` / `:share remove <notebook> <email>`.
- If you want to unshare entirely (all recipients at once), use `:share delete <notebook>` instead.
- 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
- Distinguish `remove` (one recipient) from `delete` (all recipients) in scripts.
- Validate the email argument is non-empty before invoking add/remove.
- Surface the `delete` alternative when no user is supplied.
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
- No recipient found with email ${email}
- [notebook] is required
- Unknown subcommand: ${args.command}
- No share found for folder ${folderId}
- No share found for folder ${folder.id}
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/19ed228e67ef9226.
Report an issue: GitHub.