Skip to content

fix(backfill): store full UIMessage in chat_messages.parts#627

Merged
sweetmantech merged 1 commit into
testfrom
fix/backfill-parts-full-uimessage
May 29, 2026
Merged

fix(backfill): store full UIMessage in chat_messages.parts#627
sweetmantech merged 1 commit into
testfrom
fix/backfill-parts-full-uimessage

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

Summary

The chat-workflow cutover (recoupable/chat#1747) needs migrated history to read back through the workflow path. That surfaced a shape mismatch in the Phase 2 backfill:

  • Native writes store the entire UIMessage in chat_messages.partspersistLatestUserMessage.ts:42 and persistAssistantMessage.ts:27 both do parts: message as never.
  • The read path getSessionChatHandler.ts:44 returns messages: rows.map(r => r.parts) and expects each to already be a UIMessage.
  • The backfill wrote parts: content.parts — just the bare parts array — so migrated rows came back with no id/role and wouldn't render.

This stores { id, role, parts } to match the native shape.

Data already corrected

The ~46k already-migrated rows were fixed in place on prod via a one-time, reversible SQL reconstruction from the existing columns (the original array is preserved nested under parts):

UPDATE chat_messages
SET parts = jsonb_build_object('id', id, 'role', role, 'parts', parts)
WHERE jsonb_typeof(parts) = 'array';  -- targets only backfilled rows; native rows are objects

Post-update verification: 46,616 object rows / 0 array rows; 0 rows missing id/role/parts keys; 0 rows where inner id/role diverge from the columns.

This code change keeps the owed pre-promotion straggler re-run consistent (write-once, so it only adds new rows in the correct shape).

Test plan

  • migrateRoom unit test asserts parts is the full { id, role, parts } UIMessage. (4 tests green)
  • tsc --noEmit clean on changed files; eslint clean.
  • Prod data reconciled + verified (counts above).

🤖 Generated with Claude Code

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) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 29, 2026 4:20pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • scripts/backfill/__tests__/migrateRoom.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by none
  • scripts/backfill/migrateRoom.ts is excluded by none and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 95435fab-d7fa-46bf-a82e-b0020583e696

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/backfill-parts-full-uimessage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant B as Backfill Script
    participant DB as Database
    participant R as Read Path
    participant W as Native Write Path

    Note over B,W: Chat Messages Data Flow

    B->>B: Read migrated rows
    B->>DB: Insert chat_messages row
    Note over B,DB: parts: { id, role, parts } (full UIMessage)

    alt Native write path
        W->>DB: insert chat_messages.parts as UIMessage
        Note over W,DB: persistLatestUserMessage / persistAssistantMessage
        W->>W: parts = message as never (full UIMessage)
    end

    DB-->>R: Return rows

    R->>R: messages = rows.map(r => r.parts)
    Note over R: Expects each .parts to be UIMessage

    alt parts is full UIMessage object
        R->>R: Extract id, role, parts from object
        R-->>R: Render correctly
    else parts is bare array (legacy backfill)
        R->>R: Missing id and role
        R-->>R: Render failure (no id/role)
    end

    Note over B,R: Fix: Backfill now stores { id, role, parts }<br/>matching native shape

    opt Production data fix (already applied)
        B->>DB: UPDATE chat_messages
        Note over B,DB: Reconstruct from columns<br/>where parts is array
        DB-->>B: Verify: 0 array rows<br/>0 missing id/role/parts
    end
Loading

Auto-approved: This is a focused, low-risk fix to a backfill script that corrects the stored shape of chat messages to match the production read path, has a corresponding test update, and the production data was already reconciled via a separate SQL command.

Re-trigger cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant