# False “Document changed in storage” conflict with SharePoint on 26.04 after successful PutFile without LastModifiedTime

**URL:** https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966
**Category:** User Support
**Created:** [August 22, 2026, 12:48pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966 "2026-08-22T12:48:23Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![nsergiu](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.collaboraonline.com/nsergiu/32/2388_2.png) [@nsergiu](https://forum.collaboraonline.com/u/nsergiu)
#### Post date: [August 22, 2026, 12:48pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/1 "2026-08-22T12:48:24Z")

</div>

Hi,

I believe I have identified a false document-conflict condition when using Collabora Online 26.04 with Microsoft SharePoint as the native WOPI host.

**Environment**

Collabora Online: 26.04.3.1  
WOPI host: Microsoft SharePoint Server  
WOPI endpoint: native `/_vti_bin/wopi.ashx` implementation  
Editing and WOPI locking otherwise work.

The problem occurs intermittently after a successful save. Users can receive a “Document changed in storage” / timestamp mismatch even though the apparent external modification is actually Collabora’s own preceding PutFile.

A representative sequence from the COOLWSD log is:

```auto
09:18:07
Invalid or missing JSON in WOPI::PutFile HTTP_OK response.
Expected json object with a LastModifiedTime value

09:18:09
Document [...] has been modified behind our back.
Expected: 2026-08-17T12:32:45.0000000Z
Actual: 2026-08-21T09:18:07.0000000Z

```

The significant detail is that SharePoint’s new `LastModifiedTime` is `09:18:07`, exactly the second in which Collabora performed the successful PutFile.

Looking at the current `distro/collabora/co-26.04` source, I think the sequence is:

```auto
Collabora knows timestamp T0
        |
        v
PutFile to SharePoint
        |
        v
SharePoint successfully saves the file and changes timestamp to T1
        |
        v
SharePoint returns HTTP 200 without a JSON LastModifiedTime
        |
        v
WopiStorage logs:
"Invalid or missing JSON ... Expected ... LastModifiedTime"
        |
        v
The old T0 remains cached in WopiStorage
        |
        v
DocumentBroker::handleUploadToStorageSuccessful()
copies _storage->getLastModifiedTime() into StorageManager
        |
        v
Next CheckFileInfo returns T1
        |
        v
T0 != T1
        |
        v
"Document has been modified behind our back"

```

The relevant code appears to be in `wsd/wopi/WopiStorage.cpp`, in `WopiStorage::handleUploadToStorageResponse()`.

For HTTP 200, the code tries to parse the response as JSON and extract `LastModifiedTime`. However, in the branch where the HTTP 200 response is not valid JSON, it only logs the warning. It does not invalidate the timestamp that existed before the PutFile.

This seems problematic for Microsoft SharePoint because Microsoft’s WOPI PutFile specification defines `200 OK` as success and does not require a JSON response body containing `LastModifiedTime`. `X-WOPI-ItemVersion` is an optional response header. In contrast, Collabora’s external-change extension expects LastModifiedTime to be available from PutFile.

Would it therefore make sense for the HTTP-200/no-LastModifiedTime case to invalidate the old cached timestamp?

For example, conceptually:

```auto
if (details.httpResponseCode == http::StatusCode::OK)
{
    result.setResult(StorageBase::UploadResult::Result::OK);

    if (valid JSON containing LastModifiedTime)
    {
        setLastModifiedTime(lastModifiedTime);
    }
    else
    {
        // PutFile succeeded, but the upload itself may have
        // changed LastModifiedTime in storage. The timestamp
        // from before PutFile is no longer trustworthy.
        setLastModifiedTimeUnSafe();
    }
}

```

Then `DocumentBroker` could reuse its existing `DocumentState::Activity::SyncFileTimestamp` mechanism to perform CheckFileInfo and establish the post-upload timestamp.

There is already very similar recovery logic for the case where a PutFile fails/times out and Collabora cannot know whether the WOPI host actually committed the upload. It seems that `HTTP 200 + no post-upload LastModifiedTime` has a similar timestamp state: the upload itself is known to have succeeded, but its resulting storage timestamp is unknown.

I would propose:

```auto
PutFile -> HTTP 200 + LastModifiedTime
    -> existing behaviour

PutFile -> HTTP 200 without usable LastModifiedTime
    -> mark cached LastModifiedTime unsafe
    -> successful save remains successful
    -> run CheckFileInfo / SyncFileTimestamp
    -> adopt returned timestamp as the new baseline

```

This should avoid the false conflict without disabling external-change detection. A genuine later modification would still change the timestamp from the newly synchronized value and trigger the normal conflict logic.

As a fallback, if the document closes before CheckFileInfo can be executed, the next CheckFileInfo should establish the first non-empty timestamp as the new baseline rather than comparing it with the now-invalid pre-upload value.

While reviewing the existing SyncFileTimestamp code I also noticed this condition:

```auto
if (_storageManager.getSizeAsUploaded() == size ||
    _storageManager.getSizeOnServer())

```

`getSizeOnServer()` returns `std::size_t`, so unless I am missing something, I think the second part should be:

```auto
_storageManager.getSizeOnServer() == size

```

Otherwise any non-zero previous server size makes the whole condition true.

Could someone from the Collabora team confirm:

1. Is a JSON `LastModifiedTime` in a successful PutFile response intentionally considered mandatory for SharePoint integration?
2. Would marking the old timestamp unsafe and invoking the existing `SyncFileTimestamp` path be the preferred fix for a standards-compliant `200 OK` without that JSON value?
3. Is there perhaps already a SharePoint-related patch/hotfix for this in a newer/private branch?
4. Does the `getSizeOnServer()` condition above indeed look like a missing `== size` comparison?

I can provide additional sanitized COOLWSD logs if useful.

Thanks.

---

_[View the full topic](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966)._
