laurent22/joplin · error
Cannot change the note lock of a deleted note
Error message
Cannot change the note lock of a deleted note
What it means
A deleted note (deleted_time set, i.e. in trash) cannot have its lock state changed. The check exists so lock transitions never operate on trashed notes, even when invoked directly via commands or plugins.
Solutions
- Restore the note from trash before changing its lock state.
- Filter out notes with a truthy `deleted_time` before invoking the command.
- Catch the error and skip trashed notes in batch operations.
- Permanently delete or restore, then retry.
Example fix
// before
await joplin.commands.execute('enableNoteLock', noteId);
// after
const note = await Note.load(noteId);
if (note && !note.deleted_time) await joplin.commands.execute('enableNoteLock', noteId); Defensive patterns
Strategy: validation
Validate before calling
const note = await Note.load(noteId);
if (note && !note.deleted_time) await joplin.commands.execute('enableNoteLock', noteId); Type guard
const isActiveNote = (n: NoteEntity): boolean => !n.deleted_time;
Try / catch
try { await enableNoteLock(id); } catch (e) { if (e.message.includes('deleted note')) skip(id); else throw e; } Prevention
- Exclude trashed notes (deleted_time set) from batch lock operations
- Restore notes before changing their state
- Filter note lists by deleted_time before automation runs
When it happens
Trigger: Calling enableNoteLock or disableNoteLock on a note whose `deleted_time` is non-zero — typically after the note was moved to trash but not permanently purged.
Common situations: Batch scripts that lock notes while some are already trashed; plugins iterating a list containing trashed notes; restoring flows that attempt to lock a note mid-delete.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot change the note lock of a conflict note
- No such note
- Note lock is not enabled
- Ambiguous notebook " ". Please use notebook id instead -…
- Ambiguous notebook " ". Please use short notebook id…
AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17).
Data as JSON: /api/errors/6329bd6ae200e7a1.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/services/noteLock/setNoteLockState.ts:17
import { ModelType } from '../../BaseModel';
import BaseItem from '../../models/BaseItem';
import ItemChange from '../../models/ItemChange';
import Note from '../../models/Note';
import Setting from '../../models/Setting';
import { itemIsReadOnlySync, ItemSlice } from '../../models/utils/readOnly';
import { NoteEntity } from '../database/types';
import eventManager, { EventName } from '../../eventManager';
import isNoteLockEnabled from './isNoteLockEnabled';
import NoteLockSession from './NoteLockSession';
// The UI hides the enable/disable actions for these cases, but the commands can also be
// invoked directly (keyboard, plugins), so the transitions fail closed here too.
const checkCanChangeLockState = (note: NoteEntity, noteId: string) => {
if (!isNoteLockEnabled()) throw new Error('Note lock is not enabled');
if (!note) throw new Error(`No such note: ${noteId}`);
if (note.deleted_time) throw new Error('Cannot change the note lock of a deleted note');
if (note.is_conflict) throw new Error('Cannot change the note lock of a conflict note');
if (itemIsReadOnlySync(ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache)) throw new Error('Cannot change the note lock of a read-only note');
if (!NoteLockSession.instance().isUnlocked()) throw new Error('Cannot change the note lock while the session is locked');
};
const validationFields = ['id', 'is_locked', 'deleted_time', 'is_conflict', 'share_id'];
// These only validate and emit: the note screen listens for the event and persists the
// change with a scheduled gated save.
export const enableNoteLock = async (noteId: string) => {
const note = await Note.load(noteId, { fields: validationFields });
checkCanChangeLockState(note, noteId);
if (note.is_locked) throw new Error(`Note is already locked: ${noteId}`);
eventManager.emit(EventName.NoteLockNoteStateChange, { noteId, isLocked: true });
};
export const disableNoteLock = async (noteId: string) => {
const note = await Note.load(noteId, { fields: validationFields });View on GitHub (pinned to 981a03c5c9)