{"record":{"id":"db4e6189161fd92e","repo":"laurent22/joplin","slug":"remote-item-s-has-an-updated-time-in-the-future","errorCode":null,"errorMessage":"Remote item %s has an updated_time in the future","messagePattern":"Remote item (.+?) has an updated_time in the future","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/Synchronizer.ts","lineNumber":652,"sourceCode":"\t\t\t\t\t\tlet local = locals[i];\n\t\t\t\t\t\tconst ItemClass = BaseItem.itemClass(local);\n\t\t\t\t\t\tconst path = BaseItem.systemPath(local);\n\n\t\t\t\t\t\t// Safety check to avoid infinite loops.\n\t\t\t\t\t\t// - In fact this error is possible if the item is marked for sync (via sync_time or force_sync) while synchronisation is in\n\t\t\t\t\t\t//   progress. When force_sync is not true, this is because the user is typing while the sync is running, so we should continue\n\t\t\t\t\t\t//   looping, as we don't want the sync to stop when there are still un-synced outgoing changes, otherwise this creates a race condition\n\t\t\t\t\t\t//   on mobile, where additional changes made during upload are not synced and don't trigger another sync, whereas a change made immediately\n\t\t\t\t\t\t//   after the sync has finished will trigger another sync. Once the user has stopped typing, it can then break out of the loop and continue\n\t\t\t\t\t\t//   the rest of the process.\n\t\t\t\t\t\t// - It can also happen if the item is directly modified in the sync target, and set with an update_time in the future. In that case,\n\t\t\t\t\t\t//   the local sync_time will be updated to Date.now() but on the next loop it will see that the remote item still has a date ahead\n\t\t\t\t\t\t//   and will see a conflict. There's currently no automatic fix for this - the remote item on the sync target must be fixed manually\n\t\t\t\t\t\t//   (by setting an updated_time less than current time).\n\t\t\t\t\t\tif (donePaths.indexOf(path) >= 0) {\n\t\t\t\t\t\t\tconst syncItem = await BaseItem.syncItem(syncTargetId, local.id, { fields: ['force_sync'] });\n\t\t\t\t\t\t\tif (local.updated_time > time.unixMs() + Day) {\n\t\t\t\t\t\t\t\tthrow new Error(sprintf('Remote item %s has an updated_time in the future', path));\n\t\t\t\t\t\t\t} else if (local.updated_time > time.unixMs()) {\n\t\t\t\t\t\t\t\tthrow new JoplinError(sprintf('Processing a path that has already been done: %s. Remote item has an updated_time in the future', path), 'processingPathTwice');\n\t\t\t\t\t\t\t} else if (syncItem.force_sync) {\n\t\t\t\t\t\t\t\tthrow new JoplinError(sprintf('Processing a path that has already been done: %s. Item was marked for sync using force_sync', path), 'processingPathTwice');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthrow new JoplinError(sprintf('Processing a path that has already been done: %s. The user is making changes while the sync is in progress', path), 'changedDuringSync');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst remote: RemoteItem = result.neverSyncedItemIds.includes(local.id) ? null : await this.apiCall('stat', path);\n\t\t\t\t\t\tlet action: SyncAction = null;\n\t\t\t\t\t\tlet itemIsReadOnly = false;\n\t\t\t\t\t\tlet reason = '';\n\t\t\t\t\t\tlet remoteContent = null;\n\n\t\t\t\t\t\tconst getConflictType = (conflictedItem: { type_?: number }) => {\n\t\t\t\t\t\t\tif (conflictedItem.type_ === BaseModel.TYPE_NOTE) return SyncAction.NoteConflict;\n\t\t\t\t\t\t\tif (conflictedItem.type_ === BaseModel.TYPE_RESOURCE) return SyncAction.ResourceConflict;","sourceCodeStart":634,"sourceCodeEnd":670,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/Synchronizer.ts#L634-L670","documentation":"A plain Error thrown inside the DELTA loop when a remote item's local copy has updated_time more than a full Day (24h) ahead of the current wall clock. The code comment is explicit: there is no automatic fix. The remote item on the sync target was manually edited (or corrupted) with a future timestamp far beyond now, so every sync loop re-detects the same conflict.","triggerScenarios":"Encountered when donePaths already contains the path (re-processing) AND local.updated_time exceeds time.unixMs() + Day. This happens after a previous sync pulled down a remote item whose updated_time was set more than 24h in the future — e.g. someone edited the .md file directly on the WebDAV/Joplin Server target and wrote a wrong timestamp.","commonSituations":"Manual editing of items directly on the sync target (filesystem, WebDAV, S3) with clock skew or hand-edited metadata; a device with a badly wrong system clock previously synced the item.","solutions":["Manually fix the remote item on the sync target so its updated_time is less than the current time (as the code comment instructs).","If the remote target is a filesystem/WebDAV, edit the JSON body of the item and set updated_time to a past value.","Correct the system clock on the device that produced the bad timestamp to prevent recurrence.","As a last resort, delete the offending remote item and let Joplin recreate it from the local copy."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before syncing, detect items with implausible future timestamps\nconst Day = 24 * 60 * 60 * 1000;\nconst now = Date.now();\nconst bad = items.filter(i => i.updated_time > now + Day);\nif (bad.length) {\n  // flag for manual fix on the sync target before syncing\n  reportFutureTimestampItems(bad);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await synchronizer.start(options);\n} catch (error) {\n  if (/updated_time in the future/.test(error.message)) {\n    // surface the path to the user for manual remote fix\n    showManualFixGuidance(error.message);\n    return;\n  }\n  throw error;\n}","preventionTips":["Keep system clocks NTP-synced on all devices to avoid future-dated items.","Avoid editing items directly on the sync target with hand-crafted timestamps.","Monitor sync logs for recurring 'future' errors and fix the named remote items promptly."],"tags":["sync","timestamp","conflict","manual-fix"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}