Skip to content

xsync re-uploads Office files every run: SharePoint mutates docx/xlsx/pptx on upload (1.5.1) #4

Description

@anderix

Summary

xsync re-uploads Office files (.docx, .xlsx, .pptx) on every run, even when the local source has not changed. The cause is server-side: SharePoint rewrites Office documents on upload to bind them to the library's content type, so the stored remote file never matches the local source byte-for-byte. xsync's change detection compares the remote against the local file, so it always concludes the file changed and re-uploads.

This is distinct from #3 (dropped fileSystemInfo mtime PATCH), but shares the same root assumption — that the remote reflects what was uploaded.

Environment

  • xsync 1.5.1
  • Target: SharePoint Online document library (Microsoft 365 / Graph)
  • Observed while publishing built deliverables to a library via xsync.

What happens

After a clean upload, an immediate --dry-run wants to re-upload the same files:

$ xsync ./dist "https://tenant.sharepoint.com/Shared Documents/folder"
Done: 2 created, 2 copied, 0 deleted (0 up to date).

$ xsync --dry-run ./dist "https://tenant.sharepoint.com/Shared Documents/folder"
Dry run — no changes will be made:
upload   Deliverable 1/Excelano Assessment.docx
upload   Deliverable 1/Supporting/Inventory.xlsx
Would change: 0 created, 2 copied, 0 deleted (0 up to date).

Evidence: SharePoint mutates the file on upload

Downloading the just-uploaded .docx back and comparing it to the local source:

  • Size changed on upload: 9769 bytes → 17216 bytes.
  • The remote copy gained content-type binding parts that were never in the source:
customXml/item1.xml
customXml/item2.xml
customXml/item3.xml
customXml/itemProps1.xml
customXml/itemProps2.xml
customXml/itemProps3.xml
customXml/_rels/item1.xml.rels
customXml/_rels/item2.xml.rels
customXml/_rels/item3.xml.rels
  • docProps/custom.xml gained a ContentTypeId property:
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="ContentTypeId">
  <vt:lpwstr>0x010100ABAE2669E0DAFF4D82C93F91D713F107</vt:lpwstr>
</property>
  • The actual document body (word/document.xml) is byte-identical — only SharePoint's metadata was added. The deliverable content is intact; only the container bytes differ.

Because the size differs outright, the size+mtime fast path flags the file as changed before the v1.5.1 quickXorHash fallback ever applies — and even if sizes matched, the injected parts would change the hash. So the content-hash fallback cannot rescue this case.

Reproduction

  1. Build or take any local .docx/.xlsx/.pptx.
  2. xsync ./local "https://<tenant>.sharepoint.com/<library>/<folder>"
  3. xsync --dry-run ./local "https://<tenant>.sharepoint.com/<library>/<folder>"
  4. Observe the files listed for re-upload.
  5. (Optional) Download the remote copy and unzip -l it: the customXml/* parts and the ContentTypeId custom property are present and the byte size differs from the source.

Plain-text and other non-Office files are not rewritten and sync correctly.

Suggested direction

A stateless remote-vs-local comparison can't see through a server that mutates the file on ingest. The standard fix is a local last-synced-state cache (xsync already owns ~/.config/xsync/): record each uploaded path's local size/mtime/quickXorHash at upload time, and on subsequent runs skip when the local file is unchanged since then — independent of whatever SharePoint did to the remote. This is the rsync model.

A useful property: this would also subsume #3, since both issues stem from trusting the remote's mutable state rather than a record of what was sent.

Tradeoff to weigh: it makes xsync stateful (cache drift if the remote is edited out-of-band, or the cache is wiped). A conservative design keeps the existing stateless comparison authoritative and uses the cache only as a fast path to suppress redundant uploads when the local file is provably unchanged — never to skip a genuine change. On a cold cache it falls back to current behavior (one re-upload, then cached).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions