{"record":{"id":"82fcf0e62f2b1605","repo":"laurent22/joplin","slug":"mustupgradeapp","errorCode":"mustUpgradeApp","errorMessage":"In order to synchronise, please upgrade your application to version %s+","messagePattern":"In order to synchronise, please upgrade your application to version (.+?)\\+","errorType":"exception","errorClass":"JoplinError","httpStatus":null,"severity":"critical","filePath":"packages/lib/services/synchronizer/syncInfoUtils.ts","lineNumber":617,"sourceCode":"\tsyncInfo.ppk = ppk;\n\tsaveLocalSyncInfo(syncInfo);\n}\n\nexport function masterKeyById(id: string) {\n\treturn localSyncInfo().masterKeys.find(mk => mk.id === id);\n}\n\nexport const checkIfCanSync = (s: SyncInfo, appVersion: string) => {\n\tconst isForwardCompatible = () => {\n\t\t// Forward compatibility: This version of Joplin supports the Joplin 3.7 sync target format\n\t\tif (s.appMinVersion !== forwardCompatibleAppMinVersion) return false;\n\t\t// Older Joplin versions don't support sync targets with locked notes\n\t\tif (s.noteLockKey !== null) return false;\n\t\treturn true;\n\t};\n\n\tif (compareVersions(appVersion, s.appMinVersion) < 0 && !isForwardCompatible()) {\n\t\tthrow new JoplinError(_('In order to synchronise, please upgrade your application to version %s+', s.appMinVersion), ErrorCode.MustUpgradeApp);\n\t}\n};\n","sourceCodeStart":599,"sourceCodeEnd":620,"githubUrl":"https://github.com/laurent22/joplin/blob/683240968be863e9657d767aa9f4103ff666539a/packages/lib/services/synchronizer/syncInfoUtils.ts#L599-L620","documentation":"checkIfCanSync() throws JoplinError with code MustUpgradeApp when the sync target's recorded minimum app version (s.appMinVersion, read from the target's info.json) is greater than the running app version. The only escape hatch is forward compatibility: the target's appMinVersion is exactly '3.7.0' (the Joplin 3.7 format this client understands) and the target has no locked notes (noteLockKey === null). Otherwise the client refuses to sync because a newer client has already written a format it cannot safely read.","triggerScenarios":"A device running an older Joplin syncs against a target that a newer Joplin (which bumped appMinVersion in info.json) has already upgraded; downgrading the app after a newer client used the same sync target; a fresh install of an old build pointed at a shared target; the target contains locked notes (noteLockKey set), which kills the forward-compat exception even at appMinVersion 3.7.0.","commonSituations":"Mixed-version fleets: one device auto-updates to a new release, syncs, and raises the target's minimum version; Linux distro or portable builds pinned to old versions syncing with up-to-date phones; rolling back a client to work around a regression and finding sync locked out; CI with a cached old sync target.","solutions":["Update Joplin on this device to at least the version named in the message (s.appMinVersion) — this is the intended fix and non-negotiable for a genuinely newer target format.","If all clients are meant to be old, recreate or point to a sync target that was never touched by a newer client (old targets keep their lower appMinVersion).","For app builders embedding this library, call checkIfCanSync(syncInfo, appVersion) up front and show an upgrade prompt instead of letting the error abort mid-sync.","Do not attempt to edit info.json's appMinVersion manually — a newer format may genuinely be present and older clients will corrupt it."],"exampleFix":"// before\ntry {\n\tawait synchronizer().start();\n} catch (error) {\n\t// generic handler; sync fails cryptically mid-run\n}\n\n// after\ncheckIfCanSync(localSyncInfo(), appVersion); // throws MustUpgradeApp early with a clear message\ntry {\n\tawait synchronizer().start();\n} catch (error) {\n\tif (error.code === ErrorCode.MustUpgradeApp) {\n\t\treturn showUpgradePrompt(error.message); // never retry\n\t}\n\tthrow error;\n}","handlingStrategy":"validation","validationCode":"import { compareVersions } from '@joplin/lib/utils/compareVersions';\n\nconst syncInfo = localSyncInfo();\nif (compareVersions(appVersion, syncInfo.appMinVersion) < 0) {\n\t// Show 'upgrade required' UI before starting sync; do not attempt the sync\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait sync();\n} catch (error) {\n\tif (error.code === ErrorCode.MustUpgradeApp) {\n\t\t// Unrecoverable by retrying: prompt the user to upgrade the app, then stop\n\t} else {\n\t\tthrow error;\n\t}\n}","preventionTips":["Call checkIfCanSync(syncInfo, appVersion) at sync start and translate MustUpgradeApp into an upgrade prompt.","Keep all clients on a sync target within the same major version; upgrade every device, not just one.","Never downgrade a client below the sync target's appMinVersion, and never hand-edit info.json to bypass the check.","For test/CI sync targets, recreate them with the oldest client you support."],"tags":["joplin","sync","version-compatibility","upgrade-required","sync-target"],"backgroundTag":"app-version-too-old","analyzedSha":"683240968be863e9657d767aa9f4103ff666539a","analyzedAt":"2026-08-21T12:05:35.031Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}