{"record":{"id":"66ec7ad1190c33e7","repo":"laurent22/joplin","slug":"no-such-note-noteid-setnotelockstate","errorCode":null,"errorMessage":"No such note: ${noteId}","messagePattern":"No such note: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/lib/services/noteLock/setNoteLockState.ts","lineNumber":16,"sourceCode":"import { ModelType } from '../../BaseModel';\nimport BaseItem from '../../models/BaseItem';\nimport ItemChange from '../../models/ItemChange';\nimport Note from '../../models/Note';\nimport Setting from '../../models/Setting';\nimport { itemIsReadOnlySync, ItemSlice } from '../../models/utils/readOnly';\nimport { NoteEntity } from '../database/types';\nimport eventManager, { EventName } from '../../eventManager';\nimport isNoteLockEnabled from './isNoteLockEnabled';\nimport NoteLockSession from './NoteLockSession';\n\n// The UI hides the enable/disable actions for these cases, but the commands can also be\n// invoked directly (keyboard, plugins), so the transitions fail closed here too.\nconst checkCanChangeLockState = (note: NoteEntity, noteId: string) => {\n\tif (!isNoteLockEnabled()) throw new Error('Note lock is not enabled');\n\tif (!note) throw new Error(`No such note: ${noteId}`);\n\tif (note.deleted_time) throw new Error('Cannot change the note lock of a deleted note');\n\tif (note.is_conflict) throw new Error('Cannot change the note lock of a conflict note');\n\tif (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');\n\tif (!NoteLockSession.instance().isUnlocked()) throw new Error('Cannot change the note lock while the session is locked');\n};\n\nconst validationFields = ['id', 'is_locked', 'deleted_time', 'is_conflict', 'share_id'];\n\n// These only validate and emit: the note screen listens for the event and persists the\n// change with a scheduled gated save.\nexport const enableNoteLock = async (noteId: string) => {\n\tconst note = await Note.load(noteId, { fields: validationFields });\n\tcheckCanChangeLockState(note, noteId);\n\tif (note.is_locked) throw new Error(`Note is already locked: ${noteId}`);\n\teventManager.emit(EventName.NoteLockNoteStateChange, { noteId, isLocked: true });\n};\n\nexport const disableNoteLock = async (noteId: string) => {","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/laurent22/joplin/blob/981a03c5c9e88130bccff4db47c411d35ec7ae2c/packages/lib/services/noteLock/setNoteLockState.ts#L1-L34","documentation":"checkCanChangeLockState throws this when the note entity passed to enableNoteLock/disableNoteLock is null, i.e. no note with the given noteId exists. The lock-state change cannot proceed on a nonexistent note.","triggerScenarios":"Invoking the note lock enable/disable commands with a noteId that was deleted, mistyped, or never created, so `Note.load` returned null and a null note was forwarded.","commonSituations":"Plugins holding stale note IDs after a delete; race conditions where the note is deleted between listing and locking; user typos in a command palette invocation.","solutions":["Load the note first and confirm it is non-null before calling enableNoteLock/disableNoteLock.","Refresh the note list and use a current note id.","Wrap in try-catch to handle deleted notes gracefully.","Avoid caching note ids across sessions or syncs."],"exampleFix":"// before\nawait joplin.commands.execute('enableNoteLock', noteId);\n// after\nif (await Note.load(noteId)) await joplin.commands.execute('enableNoteLock', noteId);","handlingStrategy":"validation","validationCode":"const note = await Note.load(noteId);\nif (note) await joplin.commands.execute('enableNoteLock', noteId);","typeGuard":"const isNote = (n: unknown): n is NoteEntity => !!n && typeof (n as NoteEntity).id === 'string';","tryCatchPattern":"try { await enableNoteLock(noteId); } catch (e) { if (String(e.message).startsWith('No such note')) refreshNoteList(); else throw e; }","preventionTips":["Re-fetch note ids from the model instead of caching them","Remove stale references after note deletion","Validate ids from user input before command invocation"],"tags":["note-lock","not-found","validation"],"backgroundTag":"record-not-found","analyzedSha":"981a03c5c9e88130bccff4db47c411d35ec7ae2c","analyzedAt":"2026-09-17T14:49:40.960Z","contentChangedAt":"2026-09-17T14:49:40.960Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}