laurent22/joplin · error
Cannot change the note lock while the session is locked
Error message
Cannot change the note lock while the session is locked
What it means
A generic guard in checkCanChangeLockState that blocks any lock-state change while a note lock session is active and locked (NoteLockSession.locked is true). Lock sessions exist to make a temporary lock atomic; mutating locks mid-session would break that invariant, so all enableNoteLock/disableNoteLock calls are rejected until the session is unlocked. It fires when a command is invoked during an active session, e.g. from the keyboard or a plugin, because the UI does not gate direct command invocation.
Solutions
- Unlock the note-lock session (authenticate via the lock UI/API) before changing note lock states.
- Re-run the workflow so a fresh unlocked session is established.
- Check NoteLockSession.instance().isUnlocked() before each transition.
- Shorten workflows to fit within the session timeout.
Example fix
// before
await joplin.commands.execute('disableNoteLock', noteId);
// after
if (NoteLockSession.instance().isUnlocked()) await joplin.commands.execute('disableNoteLock', noteId); Defensive patterns
Strategy: validation
Validate before calling
if (NoteLockSession.instance().isUnlocked()) await joplin.commands.execute('enableNoteLock', noteId); Type guard
const sessionUnlocked = (): boolean => NoteLockSession.instance().isUnlocked();
Try / catch
try { await disableNoteLock(id); } catch (e) { if (e.message.includes('session is locked')) await unlockSession(); else throw e; } Prevention
- Authenticate/unlock the session before lock-state workflows
- Re-check session state after long-running operations
- Handle session timeouts explicitly in automation
When it happens
Trigger: Calling enableNoteLock/disableNoteLock after the NoteLockSession was locked (e.g. timeout or explicit lock) without re-authenticating/unlocking it first.
Common situations: Long-running automation whose note-lock session expired mid-run; users who locked the session and then tried to modify another note's lock via commands/plugins.
Related errors
- Cannot change the note lock of a conflict note
- Cannot change the note lock of a deleted note
- Cannot change the note lock of a read-only note
- Cannot initialise synchroniser.
- Could not verify your identity
AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17).
Data as JSON: /api/errors/f1663c0eb8af9d04.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/services/noteLock/setNoteLockState.ts:20
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 });
checkCanChangeLockState(note, noteId);
if (!note.is_locked) throw new Error(`Note is not locked: ${noteId}`);
eventManager.emit(EventName.NoteLockNoteStateChange, { noteId, isLocked: false });View on GitHub (pinned to 981a03c5c9)