Skip to content

fix(sync): isolate unreadable local items instead of stopping folder sync - #500

Merged
veryCrunchy merged 6 commits into
mainfrom
fix/sync-dot-git-folders
Oct 8, 2026
Merged

veryCrunchy merged 6 commits into
mainfrom
fix/sync-dot-git-folders

Conversation

@veryCrunchy

@veryCrunchy veryCrunchy commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Outcome

Advances #489.

Desktop folder sync could fail an entire folder pair when one local item could not be read. Nothing treats .git, dot-folders, hidden files or extensionless names such as COMMIT_EDITMSG specially. The failure came from one bad item stopping the whole run, which Git repositories trigger often:

  1. A file that vanishes or is locked while being hashed (.git/index.lock, ORIG_HEAD, files held by an editor or antivirus) threw out of the local scan.
  2. An unreadable file or folder made the scan, preflight and staging-recovery walks rethrow.
  3. A non-regular file (for example .git/fsmonitor--daemon.ipc, or a Windows junction or placeholder) failed the scan even when an ignore rule covered it.
  4. A local file that changed during content verification (for example .git/index) aborted the run.

Changes:

  • New FileSyncLocalAvailability.kt with typed unavailable items (Vanished, Unreadable, Unsupported). Unavailable paths are withheld from both the local and server sides of planning and baselines are untouched, so a skipped item is never planned as a deletion, restore or upload. Skipped work items name each path, capped at 1,000 per run, and saved pair validation rejects reports whose reason does not match the path.
  • The local scan returns DesktopLocalSyncScan(documents, unavailable). Non-regular files covered by an ignore rule are skipped silently. An unreadable sync root, unsafe parent folders and symbolic links still stop the folder, as before.
  • Fail closed where skipping could lose data: an unreadable interrupted-replacement backup (.name.nextcloud-native-backup-<uuid>) or anything inside one stops the folder with a restore-access message, in the recovery, preflight and scan walks, so the destination is never planned as deleted while its original is stranded. An included folder at the 64-level walk depth limit stops the folder with a "nested more than 64 folders deep" message instead of being silently dropped.
  • A file that vanishes or loses access while its parent folders are being validated is reported as unavailable. Parent problems and a file replaced by a symbolic link still stop the folder.
  • A non-regular or unreadable item is reported as unavailable when it is included either as a file or as a folder on the way to a selected path, so a replaced ancestor of a selection is withheld instead of planned as a deletion.
  • Content verification leaves a file unverified only for a distinct local-revision-changed failure, I/O errors or truncation. A parent folder replaced or turned into a symbolic link after the scan stops the folder.
  • The remote scan does not descend into unavailable local paths (excludingUnavailableFileSyncPaths), so a remote listing error or size bound inside a withheld subtree no longer aborts the run. Withholding before planning remains as a second layer.
  • Recovery file naming moved to DesktopFileSyncLocalRecoveryNames.kt; DesktopFileSyncLocalTree.kt is 783 lines.
  • A local read failure during content verification leaves that file unverified instead of aborting the run.
  • The run summary reports how many local items were skipped. Summary and remote-root helpers moved to DesktopFileSyncEngineSupport.kt; the engine size baseline is lowered from 884 to 876.
  • website/content/guides/linux-folder-sync.md describes skipped items.

For reference, the official desktop client does not exclude .git by default and syncs hidden files by default. This PR adds no default exclusions.

Verification

  • Every check relevant to the changed scope passes, or each unrun check is listed below with a reason
  • bash tools/check-repository.sh passes
  • A new changes/unreleased/ fragment records the change
  • No credentials, private server data, machine-local paths, or generated output are included

Run on Windows with JDK 21:

  • Focused :ui:desktopTest for the new and updated sync test classes: pass.
  • Full :ui:desktopTest: 3787 tests, 0 failures, 69 skipped. Android compileDebugKotlinAndroid and :androidApp:testDebugUnitTest: pass.
  • bash tools/check-kotlin-architecture.sh, node tools/changelog-fragments.mjs validate, git diff --check: pass.

New tests (synthetic trees only):

  • FileSyncLocalAvailabilityTest: withholding and nested-report collapse, an unreadable .git/index or .git/objects never deleted even with deletion propagation, baselines kept, report limit, forged reasons rejected.
  • DesktopFileSyncLocalAvailabilityTest: hidden .git, COMMIT_EDITMSG, ORIG_HEAD, a vanishing index.lock, a file held open by another process, a Windows byte-range lock, a socket reported or skipped when ignored, POSIX unreadable folder and root, and a file rewritten while staged.
  • DesktopFileSyncGitRepositoryTest: runs the real engine against an in-memory WebDAV test server. A full repository uploads byte-identical and a second run makes 0 operations; a socket inside .git is skipped while everything else syncs; a locked index.lock is skipped and syncs once released; COMMIT_EDITMSG rewritten during upload syncs on the next run; with deletion propagation, moving .git/refs into an unreadable backup stops the run with no DELETE sent, and after access returns the next run makes 0 operations.
  • Fail-closed cases: an unreadable backup stops the scan and is restored intact once readable; a 64-level tree fails with and without a deep selection and succeeds when an ignore rule covers the deep branch; a lock file deleted during ancestor validation is reported as vanished; a replaced parent still stops the scan. With the backup fix temporarily disabled, both backup tests failed. Unreadable-folder tests deny listing through a Windows ACL or POSIX mode, so they now run on Windows.
  • Selection-ancestor and remote-walk cases: with deletion propagation and only .git/refs/heads/main selected, replacing .git/refs with a socket skips one item and sends no DELETE; an unlistable local .git/refs with a remote 503 for that folder completes with one skipped item. DesktopFileSyncContentSliceSafetyTest covers a same-size rewrite left unverified and a replaced parent stopping the folder without any server request. Each of these failed against the previous behavior.
  • Latest full :ui:desktopTest :androidApp:testDebugUnitTest: 3797 tests, 0 failures, 69 skipped.

Not run: the website build and guide-content test (website dependencies were not installed; frontmatter tests passed), the POSIX branch of the listing-denial tests on Linux or macOS, the symbolic-link leaf test (needs symlink privilege on the test host), and any real server or manual desktop run.

Compatibility and risk

  • The reporter's exact failure path is not confirmed because their screenshot was not reviewed. Paths 1 and 4 are the most likely on Windows.
  • Open policy questions: whether to exclude or warn about .git and other VCS metadata by default (two-way syncing a live repository between devices can corrupt it), and whether symbolic links inside a synced folder should be skipped per item instead of stopping the folder.
  • The pair inspector lists at most 20 distinct skipped reasons and the tray hides skipped items.
  • Server-side content verification failures still abort the run. The Windows Cloud Files provider path was not analysed or changed.

Visual changes

Not applicable. The only visible change is the skipped-item count in the sync run summary.

…sync

A Git working tree could fail the whole desktop folder sync when one item vanished, stayed locked, or was not a regular file during a check, for example a transient index.lock, a file held open by another process, an fsmonitor socket, or a .git/index rewritten between scan and content verification.

The local scan now reports such items individually. Their subtrees are withheld from local, remote, and baseline planning, so absence is never treated as a deletion or restore, and each item is recorded as a skipped work item with a path-specific reason. Local content-verification failures leave the candidate unverified instead of aborting the run. Unreadable sync roots and symbolic links still stop the folder.

Advances #489
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 00:33 Destroyed
@obiente-cloud

obiente-cloud Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Obiente preview

NC Native · 51a5edcad582 · Removed

View preview status

View in Obiente

Obiente updates this comment as the preview changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba755b09d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T19:10:12.892474Z 51a5edc New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 00:41 Destroyed
An interrupted-replacement backup that the recovery walk or scan cannot read may hold the only local original. It now stops the folder instead of being skipped, so its destination is never planned as a deletion. A folder delivered at the 64-level walk depth limit now stops the scan with an explicit nesting error instead of being dropped when only a selected descendant includes it. Leaf validation reads the listed file's attributes once after checking its parents, so a file that vanishes or loses access in that window is reported as unavailable, while replaced parents and symbolic links still stop the folder.

Advances #489
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 17:24 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2fd02b143

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…le remote subtrees

A non-regular item that replaces a folder included only as an ancestor of a selected path is now reported as unavailable, so its subtree is withheld instead of planned as a deletion. Remote scans no longer descend into subtrees whose local counterpart is unavailable, so a large or temporarily unlistable server folder there cannot abort the run. Content verification now isolates only a changed local generation through a distinct exception; replaced or linked parent folders stop the folder again. Owned recovery artifact naming moves to its own file.

Advances #489
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 18:05 Destroyed
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 18:12 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83c3e5623a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/content/guides/linux-folder-sync.md
Comment thread website/public/screenshots/capture-manifest.json Outdated
Comment on lines +261 to +263
override fun visitFileFailed(file: Path, exc: IOException): FileVisitResult {
requireRecoverableWalkFailure(file, exc)
return FileVisitResult.CONTINUE

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve failures observed during preflight

If a previously synchronized file is listed during preflight but vanishes or becomes unreadable before its attributes can be read, walkFileTree invokes this callback and the failure is now discarded. When the file is still absent from the subsequent scan, no unavailable item is produced, so its baseline and remote copy are planned as a local deletion and Propagate can delete the server copy. Carry preflight failures into the final unavailable set, or abort this check, rather than forgetting evidence that the path's state was ambiguous.

AGENTS.md reference: AGENTS.md:L15-L18

Useful? React with 👍 / 👎.

The Linux folder-sync guide now keeps its 2026-08-30 review date for the existing workflows and gives the unreadable-item behavior its own dated subsection, following the Windows Cloud Files guide pattern, so the banner and content metadata no longer disagree.

Advances #489
@obiente-cloud
obiente-cloud Bot temporarily deployed to Obiente Preview / PR #500 / NC Native October 8, 2026 19:05 Destroyed
@veryCrunchy
veryCrunchy merged commit 58fe5d8 into main Oct 8, 2026
6 checks passed

This branch was successfully deployed

No deployments
Obiente Preview / PR #500 / NC Native — 51a5edca Deployed Oct 8, 2026 by obiente-cloud[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant