laurent22/joplin · info · JoplinError

changedDuringSync

changedDuringSync

Error message

Processing a path that has already been done: %s. The user is making changes while the sync is in progress

What it means

A JoplinError (code 'changedDuringSync') thrown as the default branch when a path is re-encountered in donePaths but none of the special conditions (future timestamp, force_sync) apply. Per the code comment this is expected on mobile: the user keeps typing while a sync runs, generating new outgoing changes that re-add the path to the work list. The sync should continue looping, not stop.

Source

Thrown at packages/lib/Synchronizer.ts:658

						//   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;
						};

						if (!remote) {
							if (!local.sync_time) {
								action = SyncAction.CreateRemote;

View on GitHub (pinned to 2654b33620)

Solutions

  1. This is largely benign — let the sync continue; the next sync cycle will pick up the latest changes.
  2. If sync loops endlessly, stop editing briefly so the sync can break out of the upload loop and finish.
  3. On mobile, allow the sync to complete before heavy editing sessions.
Defensive patterns

Strategy: retry

Type guard

function isChangedDuringSync(e: unknown): e is JoplinError {
  return e instanceof JoplinError && (e as any).code === 'changedDuringSync';
}

Try / catch

try {
  await synchronizer.start(options);
} catch (error) {
  if (isChangedDuringSync(error)) {
    // expected on mobile while typing; the next sync cycle picks up changes
    scheduleRetry();
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: The user edits a note (or any item) while a sync is actively uploading/downloading, so the item's path reappears in the to-process list within the same sync run. Most common on mobile where typing and sync overlap.

Common situations: Active note editing on mobile during background sync; rapid successive edits on desktop while a large sync is running; external editors writing to the Joplin database concurrently.

Related errors


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