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
- Verify the sync target URL/path is correct and still contains the data (browse it via the provider's UI).
- Re-link the correct account / re-point to the original sync folder.
- If the mass deletion is genuinely intended, disable the fail-safe in sync settings (UI: Synchronisation -> Advanced -> disable fail-safe), then re-sync.
- 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
- Before disabling the fail-safe, verify the sync target path/account is correct and still contains your data.
- Don't re-point sync to an empty folder; create the folder on the original path instead.
- Confirm network drives are mounted before syncing.
- Treat a fail-safe abort as a red flag, never auto-bypass it.
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
- Cannot initialise synchroniser.
- Delta link missing: ${JSON.stringify(response)}
- Invalid WebDAV resource format: ${JSON.stringify(resource)}
- Could not get lastModified date for resource: ${JSON.stringi
- href ${href} not in baseUrl ${baseUrl} nor relativeBaseUrl $
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/212f3dde8b36e209.
Report an issue: GitHub.