laurent22/joplin · error
Cannot change the note lock of a conflict note
Error message
Cannot change the note lock of a conflict note
What it means
A generic validation guard in checkCanChangeLockState that rejects any attempt to enable or disable a lock on a note whose type is NOTE_TYPE_CONFLICT. Conflict notes are temporary merge/sync artifacts that Joplin manages internally, so a user- or plugin-driven lock change on them is never valid; the input at fault is a conflict-type NoteEntity passed to enableNoteLock or disableNoteLock (e.g. via commands invoked from keyboard shortcuts or plugins, since the UI normally hides these actions for conflict notes).
Solutions
- Resolve the conflict (merge/choose a version) so the note is no longer a conflict copy, then lock the resolved note.
- Skip notes with `is_conflict` set when batch-locking.
- Catch the error and ignore conflict notes.
- Lock the original note rather than its conflict copy.
Example fix
// before
await joplin.commands.execute('enableNoteLock', conflictNoteId);
// after
const note = await Note.load(conflictNoteId);
if (note && !note.is_conflict) await joplin.commands.execute('enableNoteLock', conflictNoteId); Defensive patterns
Strategy: validation
Validate before calling
const note = await Note.load(noteId);
if (note && !note.is_conflict) await joplin.commands.execute('enableNoteLock', noteId); Type guard
const isResolvedNote = (n: NoteEntity): boolean => !n.is_conflict;
Try / catch
try { await enableNoteLock(id); } catch (e) { if (e.message.includes('conflict note')) notify('Resolve conflict first'); else throw e; } Prevention
- Resolve sync conflicts before locking
- Skip notes in the conflict folder in automation
- Handle conflict notes as sync-system-managed
When it happens
Trigger: Invoking enableNoteLock/disableNoteLock on a note with `is_conflict === true`, which appears in the conflict folder and is produced by sync conflict resolution.
Common situations: Plugins processing all notes in the conflict folder; automation that locks recently modified notes after a sync generated conflicts.
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 deleted note
- fileNotFound
- Item not found
- No folder with id
- No such note
AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17).
Data as JSON: /api/errors/bd36f969559fb489.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/services/noteLock/setNoteLockState.ts:18
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 });
checkCanChangeLockState(note, noteId);View on GitHub (pinned to 981a03c5c9)