laurent22/joplin · critical · JoplinError

failSafe

failSafe

Error message

Fail-safe: Sync was interrupted because %d%% of the data (%d items) is about to be deleted. To override this behaviour disable the fail-safe in the sync settings.

What it means

The sync fail-safe: during delta processing, if the share of items that would be deleted is >= 90%, Joplin throws a JoplinError with code 'failSafe' instead of wiping local data. It assumes such massive deletion signals a config error (moved sync dir, disconnected drive returning an empty listing) rather than genuine user intent. Guarded by options.wipeOutFailSafe so it only fires when the loop intends to wipe.

Source

Thrown at packages/lib/file-api.ts:647

		for (let i = 0; i < itemIds.length; i++) {
			const itemId = itemIds[i];

			if (ArrayUtils.binarySearch(newContext.statIdsCache, itemId) < 0) {
				deletedItems.push({
					path: BaseItem.systemPath(itemId),
					isDeleted: true,
				});
			}
		}

		const percentDeleted = itemIds.length ? deletedItems.length / itemIds.length : 0;

		// If more than 90% of the notes are going to be deleted, it's most likely a
		// configuration error or bug. For example, if the user moves their Nextcloud
		// directory, or if a network drive gets disconnected and returns an empty dir
		// instead of an error. In that case, we don't wipe out the user data, unless
		// they have switched off the fail-safe.
		if (options.wipeOutFailSafe && percentDeleted >= 0.90) throw new JoplinError(sprintf('Fail-safe: Sync was interrupted because %d%% of the data (%d items) is about to be deleted. To override this behaviour disable the fail-safe in the sync settings.', Math.round(percentDeleted * 100), deletedItems.length), 'failSafe');

		output = output.concat(deletedItems);
	}

	newContext.deletedItemsProcessed = true;

	const hasMore = output.length >= outputLimit;

	if (!hasMore) {
		// Clear temporary info from context. It's especially important to remove deletedItemsProcessed
		// so that they are processed again on the next sync.
		newContext.statsCache = null;
		newContext.statIdsCache = null;
		delete newContext.deletedItemsProcessed;
	}

	return {
		hasMore: hasMore,

View on GitHub (pinned to 2654b33620)

Solutions

  1. Verify the sync target URL/path is correct and still contains the data (browse it via the provider's UI).
  2. Re-link the correct account / re-point to the original sync folder.
  3. If the mass deletion is genuinely intended, disable the fail-safe in sync settings (UI: Synchronisation -> Advanced -> disable fail-safe), then re-sync.
  4. Check the network drive is mounted / Nextcloud folder wasn't moved before retrying.

Example fix

// before - mass deletion blocked by fail-safe
await synchronizer.sync();
// after - confirm intent, then disable fail-safe only if deletion is real
if (userConfirmedWipe) {
  await Setting.setValue('sync.failSafe', false); // expose via sync settings UI
  await synchronizer.sync();
  await Setting.setValue('sync.failSafe', true);
}
Defensive patterns

Strategy: validation

Validate before calling

// Sanity-check the remote listing isn't empty before allowing a wipe.
if (options.wipeOutFailSafe && remoteItemCount < localItemCount * 0.1) {
  throw new Error('Refusing to sync: remote listing near-empty. Verify sync target path/account.');
}

Type guard

function isFailSafeAbort(e: any): e is { code: 'failSafe' } {
  return e && e.code === 'failSafe';
}

Try / catch

try {
  await synchronizer.sync();
} catch (e) {
  if (isFailSafeAbort(e)) {
    // Do NOT auto-disable. Prompt the user to verify the sync target first.
    throw new Error('Sync aborted by fail-safe. Verify the sync target is correct and still has your data before disabling the fail-safe.');
  }
  throw e;
}

Prevention

When it happens

Trigger: basicDelta() compares local item IDs against the remote listing; when nearly all local items are absent remotely (>=90%) and wipeOutFailSafe is set, it aborts. Caused by an empty remote listing that's actually a config/connection problem, not real deletions.

Common situations: User moved/renamed the Nextcloud/WebDAV sync folder so the listed path is empty; network drive disconnected and returns an empty dir; switched sync target accidentally; remote auth changed and returns an empty account; a server bug returns an empty listing.

Related errors


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