# 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:** 6\
**Page:** 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.

---

<div class="post-metadata">

**Author:** ![mmeeks](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.collaboraonline.com/mmeeks/32/371_2.png) [@mmeeks](https://forum.collaboraonline.com/u/mmeeks)\
**Post date:** [September 1, 2026, 7:45pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/2 "2026-09-01T19:45:14Z")

</div>

Great debugging; quite possibly we want to re-issue a CheckFileInfo if we don’t get the details back that we need; that would involve a small race, but avoid this problem.

Thanks for the great analysis !

---

<div class="post-metadata">

**Author:** ![hub](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.collaboraonline.com/hub/32/883_2.png) [@hub](https://forum.collaboraonline.com/u/hub)\
**Post date:** [September 1, 2026, 9:17pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/3 "2026-09-01T21:17:13Z")

</div>

Thanks you, this is a very useful report.

There is no private branch for SharePoint fixes: when we have any they end up on the main branch and possibly get backported. What you are reporting is definitely an issue. Whether the JSON payload is invalid or missing is something that Collabora Online ought to handle. Even if it was mandatory, Postel Law applies: we should be lenient into what we accept.

I have filed in internal ticket for us to track the issue.

Also can you tell us which version of SharePoint you are running?

---

<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:** [September 2, 2026, 11:34am UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/4 "2026-09-02T11:34:25Z")

</div>

Hi, thanks for the follow-up.

I tested it on SharePoint Server Subscription Edition, August 2026 update. (16.0.19725.20384)

---

<div class="post-metadata">

**Author:** ![hub](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.collaboraonline.com/hub/32/883_2.png) [@hub](https://forum.collaboraonline.com/u/hub)\
**Post date:** [September 15, 2026, 10:15pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/5 "2026-09-15T22:15:38Z")

</div>

There is a fix in 26.04.4. Please let us know if you can verify it fixes the issue for you.

---

<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:** [September 28, 2026, 12:44pm UTC](https://forum.collaboraonline.com/t/false-document-changed-in-storage-conflict-with-sharepoint-on-26-04-after-successful-putfile-without-lastmodifiedtime/4966/6 "2026-09-28T12:44:55Z")

</div>

Hi,

I retested with the 26.04.4 version and indeed, it fixed the false saving conflict issue.

Thanks a lot,

Sergiu.
