On the macOS File Provider client, remote changes (files created/renamed on the server) stop appearing locally, with no error shown to the user. Restarting the client does not help. Local→remote uploads may still work, so it looks like a partial/one-way stall.
Root cause: the working-set remote-change scan aborts the entire pass on the first non-404 read error. Because a change notification on macOS only ever signals .workingSet, scanMaterialisedItemsForRemoteChanges() is the single code path that pulls server→client changes into the framework. It walks the materialised items sorted parent-first (shortest remote path first — so the account root and top-level folders are read first) and does:
// Enumeration/Enumerator+WorkingSetScan.swift
} else if let readError = readResult.error, readError != .success {
logger.error("Finished remote change enumeration of materialised items with error.", [.error: readError])
break // <-- abandons the rest of the queue
}
If an early folder's depth-1 PROPFIND fails (e.g. a very large container that times out, or a transient 5xx), the break abandons every remaining folder, so no remote change for any folder is discovered. Worse, the failure is swallowed: the scan returns its partial (often empty) result with no error, and enumerateWorkingSetChanges then reports finishEnumeratingChanges(upTo: currentAnchor, moreComing: false) with a fresh "now" anchor — telling the framework the domain is fully synced. No error surfaces, and the sync point advances past changes that were never discovered.
This is intermittent and strongly correlated with large/slow accounts: the failing reads are the biggest containers (root, and folders with very many children). On a healthy account an occasional failure is masked by the next successful pass; on a large account nearly every pass fails at the same early folder, so it never catches up.
Steps to reproduce
- Use an account large enough that a top-level folder's unpaginated depth-1
PROPFIND intermittently fails/times out (or inject a transient non-404 error on an early folder read).
- On the web UI, add or rename a file in a folder that is materialised/known locally.
- Wait for a working-set sync cycle.
Expected: the new/renamed file appears locally within a cycle.
Actual: it never appears; no error is shown; restarting the client does not help.
Symptoms observed (from debug archives)
Read of URL did fail and Finished remote change enumeration of materialised items with error recur continuously; the failing URLs are the account root and the largest folders.
- The extension is not hung — working-set change-observation cycles complete every few minutes, each discovering nothing.
- Reproduces immediately after a clean restart (it is a server/account-state condition, persisted via the materialised set in the Realm DB).
- Local→remote uploads/deletes succeed in the same window (the abort only severs server→client).
Contributing issue — diagnostics blind spot
Every failure logs as an opaque NKError code 1 / domain NextcloudKit.NKError. NKError is a Swift struct with no CustomNSError conformance, so FileProviderLogDetail bridges it via as? NSError and logs the default code 1, discarding the real errorCode/errorDescription (Log/FileProviderLogDetail.swift). This hid the true failure (timeout vs 5xx vs XML-decode) and should be fixed so these are diagnosable.
Contributing issue — trashbin permanent-delete retry loop
Permanent deletion of a trashed item issues DELETE to …/dav/trashbin/<user>/trash/<name> using the stored "rough" plain filename (Item+Delete.swift handleMetadataTrashModification), but the server names collided trash entries <name>.d<deletion-timestamp>. The plain path 404s forever and macOS retries with no backoff — a chronic loop (thousands of failures against a small fixed set of items) that adds constant signalEnumerator churn and server load.
Proposed fix
- Working-set scan: replace the
break with continue — skip only the unreadable folder and keep scanning the rest of the working set; track that a read failed.
- Anchor honesty: when the scan was incomplete, do not advance the working-set sync anchor to a fresh "now" (keep the incoming anchor) so the next signal re-derives and converges instead of silently reporting "synced".
- (Trigger) paginate the working-set/change reads (as on-demand
enumerateItems already does) and/or raise the request timeout for large containers, so big-folder PROPFINDs stop failing.
- Logging: special-case
NKError in FileProviderLogDetail to log its real errorCode/errorDescription.
- Trashbin: resolve the real
.d<timestamp> trashbin name from a fresh trash listing before a permanent delete, and treat an item already absent from the trash as successfully deleted (stop the retry loop).
A regression test (RemoteChangePropagationTests) that injects a non-404 error on the first-scanned folder and asserts a later folder's change still surfaces reproduces the bug and passes after fix #1–#2.
Environment
- Nextcloud Desktop Client 33.0.7 (macOS, File Provider extension)
- macOS (File Provider / VFS mode)
- Server: recent stable
On the macOS File Provider client, remote changes (files created/renamed on the server) stop appearing locally, with no error shown to the user. Restarting the client does not help. Local→remote uploads may still work, so it looks like a partial/one-way stall.
Root cause: the working-set remote-change scan aborts the entire pass on the first non-404 read error. Because a change notification on macOS only ever signals
.workingSet,scanMaterialisedItemsForRemoteChanges()is the single code path that pulls server→client changes into the framework. It walks the materialised items sorted parent-first (shortest remote path first — so the account root and top-level folders are read first) and does:If an early folder's depth-1
PROPFINDfails (e.g. a very large container that times out, or a transient 5xx), thebreakabandons every remaining folder, so no remote change for any folder is discovered. Worse, the failure is swallowed: the scan returns its partial (often empty) result with no error, andenumerateWorkingSetChangesthen reportsfinishEnumeratingChanges(upTo: currentAnchor, moreComing: false)with a fresh "now" anchor — telling the framework the domain is fully synced. No error surfaces, and the sync point advances past changes that were never discovered.This is intermittent and strongly correlated with large/slow accounts: the failing reads are the biggest containers (root, and folders with very many children). On a healthy account an occasional failure is masked by the next successful pass; on a large account nearly every pass fails at the same early folder, so it never catches up.
Steps to reproduce
PROPFINDintermittently fails/times out (or inject a transient non-404 error on an early folder read).Expected: the new/renamed file appears locally within a cycle.
Actual: it never appears; no error is shown; restarting the client does not help.
Symptoms observed (from debug archives)
Read of URL did failandFinished remote change enumeration of materialised items with errorrecur continuously; the failing URLs are the account root and the largest folders.Contributing issue — diagnostics blind spot
Every failure logs as an opaque
NKErrorcode 1 / domain NextcloudKit.NKError.NKErroris a Swiftstructwith noCustomNSErrorconformance, soFileProviderLogDetailbridges it viaas? NSErrorand logs the defaultcode 1, discarding the realerrorCode/errorDescription(Log/FileProviderLogDetail.swift). This hid the true failure (timeout vs 5xx vs XML-decode) and should be fixed so these are diagnosable.Contributing issue — trashbin permanent-delete retry loop
Permanent deletion of a trashed item issues
DELETEto…/dav/trashbin/<user>/trash/<name>using the stored "rough" plain filename (Item+Delete.swifthandleMetadataTrashModification), but the server names collided trash entries<name>.d<deletion-timestamp>. The plain path 404s forever and macOS retries with no backoff — a chronic loop (thousands of failures against a small fixed set of items) that adds constantsignalEnumeratorchurn and server load.Proposed fix
breakwithcontinue— skip only the unreadable folder and keep scanning the rest of the working set; track that a read failed.enumerateItemsalready does) and/or raise the request timeout for large containers, so big-folder PROPFINDs stop failing.NKErrorinFileProviderLogDetailto log its realerrorCode/errorDescription..d<timestamp>trashbin name from a fresh trash listing before a permanent delete, and treat an item already absent from the trash as successfully deleted (stop the retry loop).A regression test (
RemoteChangePropagationTests) that injects a non-404 error on the first-scanned folder and asserts a later folder's change still surfaces reproduces the bug and passes after fix #1–#2.Environment