laurent22/joplin · error · Error
Note not published: %s
Error message
Note not published: %s
What it means
Thrown by the `unpublish` CLI command when the resolved note has `is_shared` set to false. The command first loads the note via loadItemOrFail (so the note exists), then guards on is_shared before calling ShareService.unshareNote — unpublishing something that was never published (or already unpublished) is rejected.
Source
Thrown at packages/app-cli/app/command-unpublish.ts:35
class Command extends BaseCommand {
public usage() {
return 'unpublish [note]';
}
public description() {
return _('Unpublishes a note from Joplin Server or Joplin Cloud');
}
public enabled() {
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
}
public async action(args: Args) {
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
if (!targetNote.is_shared) {
throw new Error(_('Note not published: %s', targetNote.title));
}
logger.info('Unshare note: ', targetNote.id);
await ShareService.instance().unshareNote(targetNote.id);
const note = await Note.load(targetNote.id);
if (note.is_shared) {
throw new Error('Assertion failure: The note is still shared.');
}
this.stdout(_('Synchronising...'));
await reg.waitForSyncFinishedThenSync();
}
}
module.exports = Command;
View on GitHub (pinned to 2654b33620)
Solutions
- Sync first (`sync`) so the local is_shared flag matches the server, then retry only if still shared.
- Check the note's share status before calling: load it and inspect is_shared.
- Confirm the sync target is Joplin Server or Joplin Cloud via `config sync.target`.
- If already unpublished, treat as success (no-op) rather than an error.
Example fix
// before
if (!targetNote.is_shared) {
throw new Error(_('Note not published: %s', targetNote.title));
}
// after — idempotent: skip if already unpublished
if (!targetNote.is_shared) {
this.stdout(_('Note is already unpublished: %s', targetNote.title));
return;
} Defensive patterns
Strategy: validation
Validate before calling
const note = await Note.load(noteId);
if (!note || !note.is_shared) {
// nothing to unpublish — skip
return;
}
await command.exec(['unpublish', noteId]); Type guard
function isPublished(note: NoteEntity | null): note is NoteEntity & { is_shared: true } {
return !!note && note.is_shared === true;
} Try / catch
try {
await command.exec(['unpublish', noteId]);
} catch (e) {
if (e.message.startsWith('Note not published')) {
// already unpublished — treat as success
} else throw e;
} Prevention
- Check note.is_shared before calling unpublish to make the operation idempotent.
- Sync before unpublish so the local flag reflects server state.
- Confirm the sync target is Joplin Server/Cloud (the command is disabled otherwise).
When it happens
Trigger: Calling the unpublish command on a note whose is_shared flag is false. This happens if the note was already unpublished, was never published to Joplin Server/Cloud, or the local sync state is stale and reflects a server-side unpublish.
Common situations: Running unpublish twice in a row; the share was removed from another client and the local DB is ahead; the note was shared by a different user and this client only has read access; connecting to a sync target that is not Joplin Server/Cloud (the command is disabled then, but a direct call bypasses enabled()).
Related errors
- Cannot find "%s".
- Cannot find "%s".
- Cannot find "%s".
- Cannot find: "%s"
- Ambiguous notebook "%s". Please use short notebook id instea
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/3a5596686ffe9b48.
Report an issue: GitHub.