{"record":{"id":"212f3dde8b36e209","repo":"laurent22/joplin","slug":"failsafe","errorCode":"failSafe","errorMessage":"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.","messagePattern":"Fail-safe: Sync was interrupted because (.+?)%% of the data \\((.+?) items\\) is about to be deleted\\. To override this behaviour disable the fail-safe in the sync settings\\.","errorType":"exception","errorClass":"JoplinError","httpStatus":null,"severity":"critical","filePath":"packages/lib/file-api.ts","lineNumber":647,"sourceCode":"\t\tfor (let i = 0; i < itemIds.length; i++) {\n\t\t\tconst itemId = itemIds[i];\n\n\t\t\tif (ArrayUtils.binarySearch(newContext.statIdsCache, itemId) < 0) {\n\t\t\t\tdeletedItems.push({\n\t\t\t\t\tpath: BaseItem.systemPath(itemId),\n\t\t\t\t\tisDeleted: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst percentDeleted = itemIds.length ? deletedItems.length / itemIds.length : 0;\n\n\t\t// If more than 90% of the notes are going to be deleted, it's most likely a\n\t\t// configuration error or bug. For example, if the user moves their Nextcloud\n\t\t// directory, or if a network drive gets disconnected and returns an empty dir\n\t\t// instead of an error. In that case, we don't wipe out the user data, unless\n\t\t// they have switched off the fail-safe.\n\t\tif (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');\n\n\t\toutput = output.concat(deletedItems);\n\t}\n\n\tnewContext.deletedItemsProcessed = true;\n\n\tconst hasMore = output.length >= outputLimit;\n\n\tif (!hasMore) {\n\t\t// Clear temporary info from context. It's especially important to remove deletedItemsProcessed\n\t\t// so that they are processed again on the next sync.\n\t\tnewContext.statsCache = null;\n\t\tnewContext.statIdsCache = null;\n\t\tdelete newContext.deletedItemsProcessed;\n\t}\n\n\treturn {\n\t\thasMore: hasMore,","sourceCodeStart":629,"sourceCodeEnd":665,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/file-api.ts#L629-L665","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before - mass deletion blocked by fail-safe\nawait synchronizer.sync();\n// after - confirm intent, then disable fail-safe only if deletion is real\nif (userConfirmedWipe) {\n  await Setting.setValue('sync.failSafe', false); // expose via sync settings UI\n  await synchronizer.sync();\n  await Setting.setValue('sync.failSafe', true);\n}","handlingStrategy":"validation","validationCode":"// Sanity-check the remote listing isn't empty before allowing a wipe.\nif (options.wipeOutFailSafe && remoteItemCount < localItemCount * 0.1) {\n  throw new Error('Refusing to sync: remote listing near-empty. Verify sync target path/account.');\n}","typeGuard":"function isFailSafeAbort(e: any): e is { code: 'failSafe' } {\n  return e && e.code === 'failSafe';\n}","tryCatchPattern":"try {\n  await synchronizer.sync();\n} catch (e) {\n  if (isFailSafeAbort(e)) {\n    // Do NOT auto-disable. Prompt the user to verify the sync target first.\n    throw new Error('Sync aborted by fail-safe. Verify the sync target is correct and still has your data before disabling the fail-safe.');\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["sync","fail-safe","data-protection","config","delta"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}