Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Receive-Only folders incorrectly detect "Local Additions" for directories due to self-induced mtime changes during sync #10574

@Finomosec

Description

@Finomosec

What happened?

Problem: In Receive-Only folders paired with Send-Only folders, Syncthing incorrectly marks newly synchronized directories as "Local Additions" after restart/rescans. This happens because:

  1. Send-Only peer announces directory with mtime T1
  2. Receive-Only creates directory and populates it → local mtime becomes T2 > T1
  3. After restart, Receive-Only compares local T2 > remote T1 → marks as "Local Addition"
  4. "Override Local Changes" works only until next restart

Key symptoms:

  • Only affects directories (typically 128B size in UI)
  • Only newly created directories during sync
  • "Override Local Changes" provides temporary relief only
  • Problem reappears after every Syncthing restart

Reproduction Steps

  1. Setup:
Peer A: Send-Only
Peer B: Receive-Only
Both: Syncthing 2.0.13 (Linux 64-bit ARM), modTimeWindowS=1
  1. Folder config (both sides):
type=receive-only (Peer B) / send-only (Peer A)
ignorePermissions=false
syncOwnership=true
receiveOwnership=true
send/receiveExtendedAttributes=false

Env: (the bug is most likely independent of these factors, but system speed might be relevant - see note below)

  • BTRFS on HDD
  • Many folders (>80)
  • Some very large folders (1.5 mio files in some folders, >5mio files total)
  • Syncthing DB is on SSD
  • Slow computers: mini-PC and raspberrypi 5.
  1. Trigger:
    • Create new directory tree on Send-Only peer (e.g. mkdir -p test1/test2) and some file inside (e.g. echo foo > test1/test2/bar)
    • Wait for sync to Receive-Only peer
  2. Observe:
Receive-Only UI shows: "test1/test2    128 B" as Local Addition

Note: Maybe this only happens for slow systems and/or very large folders or files, which would add a delay between folder creation and being done writing the file contents inside of it, which leads to a larger timestamp on the folder. On a very fast system/setup the timestamp might just be the same and the bug does not happen.

  1. Restart Syncthing on Receive-Only → Local Addition reappears

Concrete Example (stat output)

Send-Only peer (stat test1/test2/):

  Size: 176           Blocks: 0          EA Block: 4096   directory
  Access: 2026-02-11 01:57:56.386214969 +0100
  Modify: 2026-02-13 01:00:23.752101053 +0100  <- T1
  Change: 2026-02-13 01:00:23.752101053 +0100
  Birth: 2026-02-11 01:57:56.386214969 +0100
Permissions: drwxrwxr-x (0775), UID/GID: 1000/1000

Receive-Only peer (stat test1/test2/):

  Size: 176           Blocks: 0          EA Block: 4096   Verzeichnis
  Access: 2026-02-11 01:00:21.164107965 +0100
  Modify: 2026-02-14 01:00:25.305593154 +0100  <- T2 > T1
  Change: 2026-02-14 01:00:25.305593154 +0100
  Birth: 2026-02-11 01:00:21.164107965 +0100
Permissions: drwxrwxr-x (0775), UID/GID: 1000/1000  <- IDENTICAL

Result: Despite identical permissions/ownership/size, directory shows as "Local Addition" because T2 > T1.

Root Cause (from source analysis)

Directories do receive virtualMtime entries and participate in mtime-based change detection:

  1. lib/scanner/directory.go: fi.Modified = ent.ModTime().UnixNano()
  2. lib/model/stscan.go: Compares fsStat.ModTime() > db.GetVirtualMtime()
  3. lib/folder/receiveonly.go: if localMtime > remoteMtime → markAsLocalAddition()

The issue: Receive-Only logic treats self-induced mtime changes (directory updated when child files created) as legitimate local modifications.

Why documentation is misleading: "Directory mtimes not synced" refers to final-settling after sync completion, but mtimes are read during scanning/indexing and do trigger virtualMtime updates.

Workarounds (tested)

  1. touch on Send-Only: Updates remote virtualMtime → temporarily fixes
  2. Send&Receive instead of Receive-Only: Eliminates "Local Additions" UI entirely

Expected Behavior

Receive-Only should recognize directories whose contents match the global index as "Up to Date", regardless of self-induced mtime changes during population.

Suggested Fix

In lib/folder/receiveonly.go (pseudocode):

// For directories, only mark as local addition if:
// 1. Contents differ (block hash mismatch), OR  
// 2. Permissions/ownership differ, OR
// 3. Directory is empty but remote shows populated
// 4. Directory exists, but is non-existent on remote
if !contentsMatch && !metadataMatch {
    markAsLocalAddition()
}

Alternative: During directory creation in Receive-Only, preserve the announced virtualMtime from FileInfo instead of using os.Stat().ModTime() post-population.

Side-Note / Observation:

I also tried fixing the timestamps on the receive-only-side by setting them exactly as they were on the sending side.
But a rescandid not pick up the changes without a restart of Syncthing.
This probably happened, because in memory something like this happens:
localDir.mtime = max(localDir.mtime, remoteDir.mtime)
Thus if the localDir.mtime is set to a lower value as before, it would not pick up the change.
However, after a restart of Syncthing this temporary value in memory does no longer exist and the current mtime of the localDir is read and now (after the fix) no longer is larger than the remote mtime thus in sync.


Environment:

Receive-Only: Ubuntu Trixie, BTRFS, ARM64
Send-Only: Linux Mint, BTRFS, 64-bit Intel/AMD
Syncthing: v2.0.13 everywhere

Syncthing version

v2.0.13

Platform & operating system

Linux arm64

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA problem with current functionality, as opposed to missing functionality (enhancement)needs-triageNew issues needed to be validated

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions