laurent22/joplin · warning · JoplinError
processingPathTwice
processingPathTwice
Error message
Processing a path that has already been done: %s. Remote item has an updated_time in the future
What it means
A JoplinError (code 'processingPathTwice') thrown in the DELTA loop when a path is encountered again in donePaths AND local.updated_time is in the future but within one Day of now. Unlike error 142 (more than a day ahead), this is treated as a reprocessing safety trip — the item's timestamp is slightly ahead of the clock, so the loop would otherwise repeat indefinitely.
Source
Thrown at packages/lib/Synchronizer.ts:654
const path = BaseItem.systemPath(local);
// Safety check to avoid infinite loops.
// - In fact this error is possible if the item is marked for sync (via sync_time or force_sync) while synchronisation is in
// progress. When force_sync is not true, this is because the user is typing while the sync is running, so we should continue
// looping, as we don't want the sync to stop when there are still un-synced outgoing changes, otherwise this creates a race condition
// on mobile, where additional changes made during upload are not synced and don't trigger another sync, whereas a change made immediately
// 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
// the rest of the process.
// - 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,
// 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
// and will see a conflict. There's currently no automatic fix for this - the remote item on the sync target must be fixed manually
// (by setting an updated_time less than current time).
if (donePaths.indexOf(path) >= 0) {
const syncItem = await BaseItem.syncItem(syncTargetId, local.id, { fields: ['force_sync'] });
if (local.updated_time > time.unixMs() + Day) {
throw new Error(sprintf('Remote item %s has an updated_time in the future', path));
} else if (local.updated_time > time.unixMs()) {
throw new JoplinError(sprintf('Processing a path that has already been done: %s. Remote item has an updated_time in the future', path), 'processingPathTwice');
} else if (syncItem.force_sync) {
throw new JoplinError(sprintf('Processing a path that has already been done: %s. Item was marked for sync using force_sync', path), 'processingPathTwice');
} else {
throw 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');
}
}
const remote: RemoteItem = result.neverSyncedItemIds.includes(local.id) ? null : await this.apiCall('stat', path);
let action: SyncAction = null;
let itemIsReadOnly = false;
let reason = '';
let remoteContent = null;
const getConflictType = (conflictedItem: { type_?: number }) => {
if (conflictedItem.type_ === BaseModel.TYPE_NOTE) return SyncAction.NoteConflict;
if (conflictedItem.type_ === BaseModel.TYPE_RESOURCE) return SyncAction.ResourceConflict;
return SyncAction.ItemConflict;
};View on GitHub (pinned to 2654b33620)
Solutions
- Let the current sync finish; the next sync cycle usually clears it once wall time catches up.
- Verify the system clock is accurate (NTP sync) on all syncing devices.
- If persistent, manually correct the affected item's updated_time to a past value on the sync target.
- Avoid editing items directly on the sync target while a sync is running.
Defensive patterns
Strategy: retry
Validate before calling
// Check for small clock skew before syncing
const now = Date.now();
const slightlyAhead = items.filter(
i => i.updated_time > now && i.updated_time <= now + Day,
);
if (slightlyAhead.length) {
// wait until wall time catches up, or clamp the timestamp
} Type guard
function isProcessingPathTwice(e: unknown): e is JoplinError {
return e instanceof JoplinError && (e as any).code === 'processingPathTwice';
} Try / catch
try {
await synchronizer.start(options);
} catch (error) {
if (isProcessingPathTwice(error)) {
// benign clock-skew; retry on the next sync cycle
scheduleRetry();
return;
}
throw error;
} Prevention
- Ensure NTP time sync on all clients.
- Avoid editing items directly on the sync target during active sync.
- Treat 'processingPathTwice' as transient unless it persists across multiple cycles.
When it happens
Trigger: A path is processed, added to donePaths, then appears again in the same loop because local.updated_time is between time.unixMs() and time.unixMs()+Day — small clock skew or a just-saved item whose timestamp rounds up.
Common situations: Minor clock skew between the client and sync target; an item saved with a timestamp a few seconds/minutes in the future; OneDrive-style targets whose lastModifiedDateTime is reported slightly ahead.
Related errors
- Remote item %s has an updated_time in the future
- rejectedByTarget
- Could not get lastModified date for resource: ${JSON.stringi
- Invalid date: ${lastModifiedString}
- Timed out trying to get sync target clock time
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/c185701a7645473a.
Report an issue: GitHub.