laurent22/joplin · error · Error
Cannot change encrypted item
Error message
Cannot change encrypted item
What it means
BaseCommand.encryptionCheck is a guard called by mutating commands (attach, done, edit, etc.). If the supplied item has a truthy encryption_applied field — meaning the item is end-to-end encrypted on disk and has not been decrypted — it throws 'Cannot change encrypted item'. This prevents writing cleartext changes that would corrupt the encrypted blob. The message is i18n-translated.
Source
Thrown at packages/app-cli/app/base-command.ts:22
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stdout can be called with formatted strings or arbitrary values
type StdoutFn = (text: any)=> void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Prompt response varies by type and tests pass sync mocks
type PromptFn = (message: string, options: any)=> any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Redux dispatch action shape varies
type DispatcherFn = (action: any)=> any;
export default class BaseCommand {
protected stdout_: StdoutFn | null = null;
protected prompt_: PromptFn | null = null;
protected dispatcher_: DispatcherFn | null = null;
public usage(): string {
throw new Error('Usage not defined');
}
public encryptionCheck(item: { encryption_applied?: number } | null) {
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
}
public description(): string {
throw new Error('Description not defined');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Subclasses override with command-specific arg shapes; parameters are contravariant so a narrower base would break all overrides
public async action(_args: any) {
throw new Error('Action not defined');
}
public compatibleUis() {
return ['cli', 'gui'];
}
public supportsUi(ui: string) {
return this.compatibleUis().indexOf(ui) >= 0;
}View on GitHub (pinned to 2654b33620)
Solutions
- Decrypt the item first: run 'e2ee decrypt' / provide the master password so encryption_applied is cleared.
- Verify status with 'e2ee status' to confirm the key is active.
- Disable E2EE on the sync target if encryption is not required.
- Skip the mutation and inform the user that the note must be decrypted first.
Example fix
// before
// this.encryptionCheck(item);
// await modifyItem(item);
// after
// if (item?.encryption_applied) {
// throw new Error('Decrypt the note before editing (e2ee decrypt)');
// }
// await modifyItem(item); Defensive patterns
Strategy: type-guard
Validate before calling
if (item?.encryption_applied) {
throw new Error('Note is encrypted; decrypt with `e2ee decrypt` before editing.');
} Type guard
const isDecrypted = (item: { encryption_applied?: number } | null): boolean =>
!item || !item.encryption_applied; Prevention
- Run 'e2ee decrypt' / supply the master password before editing on an E2EE-enabled device.
- Check 'e2ee status' to confirm the key is active in scripts.
- Guard every mutating command with encryptionCheck (it is inherited from BaseCommand).
When it happens
Trigger: Trying to edit, attach to, or toggle a todo on a note while E2EE is enabled but the master key has not been supplied (or decryption is disabled), so the note is still stored encrypted.
Common situations: E2EE enabled on a new device before entering the master password; notes synced in encrypted form before the local client unlocked them; scripting against an encrypted profile.
Related errors
- More than one item match "%s". Please narrow down your query
- Cannot find "%s".
- No notebook selected.
- No notebook has been specified.
- The command "%s" is only available in GUI mode
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/13c2e4860de9d330.
Report an issue: GitHub.