From 6d3725973ea67f0b3dbd187a3bf6492a0b8acc54 Mon Sep 17 00:00:00 2001 From: TomasPalsson Date: Tue, 2 Jun 2026 14:27:13 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20fix:=20Strip=20null=20c?= =?UTF-8?q?ontent=20parts=20on=20message=20load=20to=20prevent=20formatAge?= =?UTF-8?q?ntMessages=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The streaming content aggregator builds message content by index and yields a sparse array; an interrupted/partial save persists a hole that serializes to null in MongoDB. On replay, @librechat/agents formatAgentMessages reads part.type with no null guard and crashes. Sanitize content holes at getMessages, the single DB read chokepoint for conversation history, so both already-corrupted and future rows are neutralized for every consumer (formatAgentMessages, token counting, edit path). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/data-schemas/src/methods/message.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/data-schemas/src/methods/message.ts b/packages/data-schemas/src/methods/message.ts index acaff7c77840..35c4d7d71dce 100644 --- a/packages/data-schemas/src/methods/message.ts +++ b/packages/data-schemas/src/methods/message.ts @@ -325,11 +325,19 @@ export function createMessageMethods(mongoose: typeof import('mongoose')): Messa async function getMessages(filter: FilterQuery, select?: string) { try { const Message = mongoose.models.Message as Model; - if (select) { - return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean(); + const query = Message.find(filter).sort({ createdAt: 1 }); + const messages = await (select ? query.select(select) : query).lean(); + /** Drop null/undefined holes from array `content`. The streaming content + * aggregator builds parts by index and yields a sparse array; an interrupted + * run can persist a hole that serializes to `null`. Downstream formatters + * (e.g. formatAgentMessages) read `part.type` and crash on it, so cleaning on + * read neutralizes both already-corrupted rows and any future ones. */ + for (const message of messages) { + if (Array.isArray(message.content)) { + message.content = message.content.filter((part) => part != null); + } } - - return await Message.find(filter).sort({ createdAt: 1 }).lean(); + return messages; } catch (err) { logger.error('Error getting messages:', err); throw err;