From 649988ce75e8dfc633988141f6f69f77f7a687b6 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Fri, 29 May 2026 11:19:17 -0500 Subject: [PATCH] fix(backfill): store full UIMessage in chat_messages.parts The workflow persists the entire UIMessage in the parts column (persistLatestUserMessage / persistAssistantMessage), and the read path (getSessionChatHandler) returns parts verbatim as a UIMessage. The Phase 2 backfill wrote only the bare parts array, so migrated history came back without id/role and wouldn't render through the workflow read path. Store { id, role, parts } to match the native shape. The already-migrated ~46k rows were corrected in place via a one-time SQL reconstruction (jsonb_build_object from the id/role/parts columns); this fix keeps the owed straggler re-run consistent. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/backfill/__tests__/migrateRoom.test.ts | 9 ++++++++- scripts/backfill/migrateRoom.ts | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/backfill/__tests__/migrateRoom.test.ts b/scripts/backfill/__tests__/migrateRoom.test.ts index 7ee86fdb8..40bd58eac 100644 --- a/scripts/backfill/__tests__/migrateRoom.test.ts +++ b/scripts/backfill/__tests__/migrateRoom.test.ts @@ -80,8 +80,15 @@ describe("migrateRoom", () => { expect(insertChat).toHaveBeenCalledTimes(1); // one batch call containing only the well-formed message expect(upsertChatMessages).toHaveBeenCalledTimes(1); + // `parts` stores the full UIMessage (matching the workflow's native + // persist path), not the bare parts array. expect(upsertChatMessages).toHaveBeenCalledWith([ - expect.objectContaining({ id: "m1", chat_id: "room-1", role: "user" }), + expect.objectContaining({ + id: "m1", + chat_id: "room-1", + role: "user", + parts: { id: "m1", role: "user", parts: [{ type: "text", text: "hi" }] }, + }), ]); expect(stats.messagesWritten).toBe(1); expect(stats.messagesMalformed).toBe(1); diff --git a/scripts/backfill/migrateRoom.ts b/scripts/backfill/migrateRoom.ts index c95209ec2..79f8c192f 100644 --- a/scripts/backfill/migrateRoom.ts +++ b/scripts/backfill/migrateRoom.ts @@ -57,11 +57,16 @@ async function migrateMessages( malformed++; continue; } + // The workflow persists the FULL UIMessage in the `parts` column + // (see lib/chat/persistLatestUserMessage / persistAssistantMessage), + // and the read path (getSessionChatHandler) returns `parts` verbatim + // expecting a UIMessage. Store the same shape so migrated history + // deserializes identically to natively-written chats. rows.push({ id: memory.id, chat_id: roomId, role: content.role, - parts: content.parts, + parts: { id: memory.id, role: content.role, parts: content.parts }, created_at: memory.updated_at, }); }