Split out of the review of LibreChat-AI#16436 (local review on head f3e160f).
What happens
When the last SSE subscriber disconnects concurrently with the run settling (completion, error, or abort), the disconnect snapshot save in api/server/controllers/agents/request.js re-reads the job record and skips when isSettledJobRecord reports a settled status. That guard is a stale read: the settling path can transition the job and write the terminal row between the guard's read and the snapshot's saveMessage, and the snapshot then lands last, reopening the settled row as unfinished: true with marker-only content.
Expected
The snapshot write is conditional at the storage layer (for example, a filtered update that only applies while the row is not already settled), or the two writers are serialized, so a settled compaction turn cannot be reopened by a late disconnect snapshot.
The guard shipped there covers the common ordering and documents the residual window; the atomic fix needs conditional updates in the shared message persistence layer (~/models saveMessage), which every writer uses and which LibreChat-AI#16436 does not touch.
Split out of the review of LibreChat-AI#16436 (local review on head f3e160f).
What happens
When the last SSE subscriber disconnects concurrently with the run settling (completion, error, or abort), the disconnect snapshot save in
api/server/controllers/agents/request.jsre-reads the job record and skips whenisSettledJobRecordreports a settled status. That guard is a stale read: the settling path can transition the job and write the terminal row between the guard's read and the snapshot'ssaveMessage, and the snapshot then lands last, reopening the settled row asunfinished: truewith marker-only content.Expected
The snapshot write is conditional at the storage layer (for example, a filtered update that only applies while the row is not already settled), or the two writers are serialized, so a settled compaction turn cannot be reopened by a late disconnect snapshot.
Why it is out of scope for LibreChat-AI#16436
The guard shipped there covers the common ordering and documents the residual window; the atomic fix needs conditional updates in the shared message persistence layer (
~/modelssaveMessage), which every writer uses and which LibreChat-AI#16436 does not touch.