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

  1. Sync first (`sync`) so the local is_shared flag matches the server, then retry only if still shared.
  2. Check the note's share status before calling: load it and inspect is_shared.
  3. Confirm the sync target is Joplin Server or Joplin Cloud via `config sync.target`.
  4. 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

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


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