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

  1. Let the current sync finish; the next sync cycle usually clears it once wall time catches up.
  2. Verify the system clock is accurate (NTP sync) on all syncing devices.
  3. If persistent, manually correct the affected item's updated_time to a past value on the sync target.
  4. 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

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


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/c185701a7645473a. Report an issue: GitHub.