{"record":{"id":"7343f45702c0e326","repo":"laurent22/joplin","slug":"note-lock-is-not-enabled","errorCode":null,"errorMessage":"Note lock is not enabled","messagePattern":"Note lock is not enabled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/lib/services/noteLock/setNoteLockState.ts","lineNumber":15,"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","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/laurent22/joplin/blob/981a03c5c9e88130bccff4db47c411d35ec7ae2c/packages/lib/services/noteLock/setNoteLockState.ts#L1-L33","documentation":"Note locking is an opt-in feature; setNoteLockState refuses any enable/disable transition when the feature flag is off. The UI hides these actions when locking is disabled, but commands can still be invoked via keyboard or plugins, so this check fails closed.","triggerScenarios":"Calling enableNoteLock or disableNoteLock (directly or via command/plugin API) while `isNoteLockEnabled()` returns false — i.e. the note-lock feature has not been turned on in settings.","commonSituations":"Plugins or users invoking the note-lock commands before enabling the feature; keyboard shortcuts registered against hidden commands.","solutions":["Enable note lock in the application settings before attempting to change a note's lock state.","Gate the command invocation behind an isNoteLockEnabled() check in your plugin/UI.","Hide or disable the command registration when the feature is off.","Catch the error and surface a 'feature not enabled' message to the user."],"exampleFix":"// before\nawait joplin.commands.execute('enableNoteLock', noteId);\n// after\nif (isNoteLockEnabled()) await joplin.commands.execute('enableNoteLock', noteId);","handlingStrategy":"validation","validationCode":"if (isNoteLockEnabled()) await joplin.commands.execute('enableNoteLock', noteId);","typeGuard":"const canLock = (): boolean => isNoteLockEnabled();","tryCatchPattern":"try { await setNoteLockState(...); } catch (e) { if (e.message === 'Note lock is not enabled') promptEnableNoteLock(); else throw e; }","preventionTips":["Check the feature flag before registering or invoking lock commands","Keep UI affordances and command availability in sync","Document the prerequisite in plugin command descriptions"],"tags":["note-lock","feature-flag","validation"],"backgroundTag":"feature-not-enabled","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"}