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

  1. Decrypt the item first: run 'e2ee decrypt' / provide the master password so encryption_applied is cleared.
  2. Verify status with 'e2ee status' to confirm the key is active.
  3. Disable E2EE on the sync target if encryption is not required.
  4. 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

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


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/13c2e4860de9d330. Report an issue: GitHub.