fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(file-provider): make materialized item reconciliation converge - #10682

Open
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence
Open

fix(file-provider): make materialized item reconciliation converge#10682
potanin wants to merge 1 commit into
nextcloud:masterfrom
potanin:fix/materialized-enumeration-convergence

Conversation

@potanin

@potaninpotanin commented Aug 26, 2026

Copy link
Copy Markdown

Hello,

I had a persistent issue with Nextcloud Mac Desktop client for a while where with my large numbers of files (250K files in one account and a whole lot more in others) when switching from classic to File Provide sync it would just sit there for weeks doing nothing but trying to sync! I finally had to resort to Claude (see below what Fable did) to figure out what is going on. The diagnosis of having a FileProvider cause it to forever enumerate seems to fit. I create an issue but since everyone is probably busy, I thought the fix seems simple enough to ask Claude Fable to fix it.

I reviewed the change, and it looks minimal, as it is a workaround around the FileProvider own report of which item was changed or not with a large regression test added to detect it again. I hope this is acceptable and I am trying to fix the client here, not get any credit so please credit Fable instead. ;-)

Hope this helps!

Cheers,
Alex.

P.S. Below is the more detailed technical description which Fable drafted:

MaterializedEnumerationObserver treats every item in the database's materialized set that is absent from the system's enumeration of materialized items as freshly evicted. Since a81b5da, an evicted directory keeps visitedDirectory == true so it stays subscribed to remote change scanning. That same flag is what keeps an item in materialisedItemMetadatas(), so every previously visited directory without local content reappeared as an eviction candidate on every reconciliation pass. Each pass rewrote its metadata row, logged "Updating item state to dataless." and reported it to the completion handler again, so reconciliation never reached a quiescent state.

On accounts with many previously browsed directories this produced a continuous re-marking loop scaling with the size of the materialized set (issue #10558 reports 81,563 dataless marks covering 17,473 distinct items within 87 minutes in a single extension process lifetime), with sustained CPU load, database growth and starvation of enumeration, fetching and upload work in the extension.

Persist and report only actual state transitions: an eviction candidate whose stored flags already match the dataless target state is now skipped entirely. Directories keep their visitedDirectory subscription, preserving the shared mount root behaviour introduced by a81b5da, and a directory carrying a stale downloaded flag is still cleared and reported, but only once.

The updated expectation in testMaterialisedObserverWithMixedState encodes the new contract: a visited directory in steady state is not reported as evicted again. The new regression test drives two reconciliation passes over a dataless visited directory, a stale downloaded directory and an evicted file, asserting that the second pass reports no transitions at all. It fails before this change and passes with it. Full package test suite: 347 XCTest and 59 Swift Testing tests pass.

The analysis, fix and tests were authored by Claude Fable 5 running in Claude Code, at the request of and operated by the contributor.

Closes#10558.

Assisted-by: ClaudeCode:claude-fable-5

Resolves

#

Summary

TODO

  • ...

Checklist

AI (if applicable)

@claucambraclaucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small nitpick but generally looks good! Thanks for the contribution

@potanin
potaninforce-pushed the fix/materialized-enumeration-convergence branch from cc4586b to 934dee8CompareAugust 27, 2026 05:27
@potanin

Copy link
Copy Markdown
Author

Offending comment now removed. Also, been running this modified client as dev on my mac and it works fine now for me at least, yay! Hope to see it make it into the release to switch back to the main. Thank you all!

@RelloRello added this to the 34.0.4 milestone Aug 27, 2026
@RelloRello added os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 📁 file provider macOS File Provider Extension, more general also known as virtual file system. labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10682.zip

Digest: sha256:ff41783833b3b8d98612d14381caad50f310c76318417dced4219546c57a510f

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

MaterializedEnumerationObserver treats every item in the database's
materialized set that is absent from the system's enumeration of
materialized items as freshly evicted. Since a81b5da, an evicted
directory keeps visitedDirectory == true so it stays subscribed to
remote change scanning. That same flag is what keeps an item in
materialisedItemMetadatas(), so every previously visited directory
without local content reappeared as an eviction candidate on every
reconciliation pass. Each pass rewrote its metadata row, logged
"Updating item state to dataless." and reported it to the completion
handler again, so reconciliation never reached a quiescent state.
On accounts with many previously browsed directories this produced a
continuous re-marking loop scaling with the size of the materialized
set (issue nextcloud#10558 reports 81,563 dataless marks covering 17,473
distinct items within 87 minutes in a single extension process
lifetime), with sustained CPU load, database growth and starvation of
enumeration, fetching and upload work in the extension.
Persist and report only actual state transitions: an eviction
candidate whose stored flags already match the dataless target state
is now skipped entirely. Directories keep their visitedDirectory
subscription, preserving the shared mount root behaviour introduced
by a81b5da, and a directory carrying a stale downloaded flag is
still cleared and reported, but only once.
The updated expectation in testMaterialisedObserverWithMixedState
encodes the new contract: a visited directory in steady state is not
reported as evicted again. The new regression test drives two
reconciliation passes over a dataless visited directory, a stale
downloaded directory and an evicted file, asserting that the second
pass reports no transitions at all. It fails before this change and
passes with it. Full package test suite: 347 XCTest and 59 Swift
Testing tests pass.
The analysis, fix and tests were authored by Claude Fable 5 running
in Claude Code, at the request of and operated by the contributor.
Closesnextcloud#10558.
Assisted-by: ClaudeCode:claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alex Potanin <potanin@gmail.com>
@mgallien
mgallienforce-pushed the fix/materialized-enumeration-convergence branch from a1c11f6 to 4a339cfCompareSeptember 2, 2026 16:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: 📁 file providermacOS File Provider Extension, more general also known as virtual file system.os: 🍎 macOSApple macOS, formerly also known as OS X

Projects

None yet

3 participants

@potanin@claucambra@Rello