Issue Description
FileNode/get should return the state on the server of all FileNode records, but the state returned is out of date.
Expected Behavior
See the FileNode/get expected response behavior from the JMAP spec.
Actual Behavior
The state returned is older than the current state.
Reproduction Steps
Request:
{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:filenode"
],
"methodCalls": [
[
"FileNode/get",
{
"accountId": "c",
"ids": null,
"properties": [
"id",
"name",
"parentId",
"nodeType",
"modified",
"size",
"blobId",
"type"
]
},
"nodes"
]
]
}
Response:
{
"methodResponses": [
[
"FileNode/get",
{
"accountId": "c",
"state": "sei",
"list": [
{
"id": "be",
"name": "apple.md",
"parentId": null,
"nodeType": "file",
"modified": "2026-06-02T14:02:15Z",
"size": 56,
"blobId": "cbrij9timsflhxzbidww1i7uq19soc3hib31x0iy99jnyyitsknkwaqmeq",
"type": "application/octet-stream"
},
{
"id": "bf",
"name": "banana.md",
"parentId": null,
"nodeType": "file",
"modified": "2026-06-02T14:02:15Z",
"size": 38,
"blobId": "caq1s9yh1uz1gvtwl2npak2ujelu7dpit91w7rbyfn7dqv7mefxgsaqmeu",
"type": "application/octet-stream"
}
],
"notFound": []
},
"nodes"
]
],
"sessionState": "9f3fd14e"
}
The returned state is “sei”.
Now, without making any changes, request all changes since “sei”. There shouldn’t be any, because “sei” was just returned, so it should be the current state.
Request:
{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:filenode"
],
"methodCalls": [
[
"FileNode/changes",
{
"accountId": "c",
"sinceState": "sei",
"maxChanges": null
},
"changes"
],
[
"FileNode/get",
{
"accountId": "c",
"#ids": {
"resultOf": "changes",
"name": "FileNode/changes",
"path": "/created"
},
"properties": [
"id",
"name",
"parentId",
"nodeType",
"modified",
"size",
"blobId",
"type"
]
},
"created"
],
[
"FileNode/get",
{
"accountId": "c",
"#ids": {
"resultOf": "changes",
"name": "FileNode/changes",
"path": "/updated"
},
"properties": [
"id",
"name",
"parentId",
"nodeType",
"modified",
"size",
"blobId",
"type"
]
},
"updated"
]
]
}
Response:
{
"methodResponses": [
[
"FileNode/changes",
{
"accountId": "c",
"oldState": "sei",
"newState": "sjq",
"hasMoreChanges": false,
"created": [
"be",
"bf"
],
"updated": [],
"destroyed": [
"w",
"z",
"s",
"k",
"j",
"t",
"o",
"m",
"l",
"n",
"u",
"r",
"q",
"p",
"i",
"c",
"bb",
"bc",
"3",
"1",
"0",
"2",
"7",
"v",
"y",
"f",
"g",
"e",
"h",
"ba",
"x",
"d",
"9",
"b"
]
},
"changes"
],
[
"FileNode/get",
{
"accountId": "c",
"state": "sei",
"list": [
{
"id": "be",
"name": "apple.md",
"parentId": null,
"nodeType": "file",
"modified": "2026-06-02T14:02:15Z",
"size": 56,
"blobId": "cbrij9timsflhxzbidww1i7uq19soc3hib31x0iy99jnyyitsknkwaqmeq",
"type": "application/octet-stream"
},
{
"id": "bf",
"name": "banana.md",
"parentId": null,
"nodeType": "file",
"modified": "2026-06-02T14:02:15Z",
"size": 38,
"blobId": "caq1s9yh1uz1gvtwl2npak2ujelu7dpit91w7rbyfn7dqv7mefxgsaqmeu",
"type": "application/octet-stream"
}
],
"notFound": []
},
"created"
],
[
"FileNode/get",
{
"accountId": "c",
"state": "sei",
"list": [],
"notFound": []
},
"updated"
]
],
"sessionState": "9f3fd14e"
}
Notice that oldState is “sei” and newState is “sjq”.
Before setting up this test with just two files for the bug report, I had deleted all folders and files using the File Manager with a WebDAV connection to Stalwart. The list of destroyed nodes refers to all those old folders and files.
When I first asked for all the ids in this new session, FileNode/get said the current state was “sei”, but according to FileNode/changes, the current state is “sjq”.
Notice also that the state in the two FileNode/get responses for the created and updated nodes. Again, it is reporting the curent state to be “sei”, when FileNode/changes has just told me that newState is “sjq”.
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on