fix(orchestration): show follow-up assistant replies after Cursor session resume#3642
Conversation
…sion resume When Cursor resumes a session, ACP reuses assistant segment IDs across turns. Scope assistant message IDs by turn and reset projection state on turn change so follow-up replies appear below the latest user message instead of updating stale segments sorted above it. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🟠 High
When the first thread.message-sent event for an attachments-only message arrives (empty text, streaming: false, no existing row), mergeThreadMessageProjection evaluates previousMessage.text on the !previousMessage branch because the condition incoming.streaming || incoming.text.length > 0 is false, so nextText falls through to turnChanged ? "" : previousMessage.text, which dereferences undefined. This throws during projection instead of persisting the message. The guard should also cover the case where there is no previous message and the incoming text is empty.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/Layers/ProjectionPipeline.ts around line 27:
When the first `thread.message-sent` event for an attachments-only message arrives (empty text, `streaming: false`, no existing row), `mergeThreadMessageProjection` evaluates `previousMessage.text` on the `!previousMessage` branch because the condition `incoming.streaming || incoming.text.length > 0` is false, so `nextText` falls through to `turnChanged ? "" : previousMessage.text`, which dereferences `undefined`. This throws during projection instead of persisting the message. The guard should also cover the case where there is no previous message and the incoming text is empty.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3a401bf. Configure here.
| if (incoming.streaming || incoming.text.length > 0) { | ||
| return incoming.text; | ||
| } | ||
| return turnChanged ? "" : previousMessage.text; |
There was a problem hiding this comment.
Merge crashes first empty upsert
Medium Severity
When mergeThreadMessageProjection processes a first message that is non-streaming and has empty text, it attempts to read previousMessage.text even though previousMessage is undefined. This causes a runtime error during DB projection upserts, where it should instead return the empty incoming text.
Reviewed by Cursor Bugbot for commit 3a401bf. Configure here.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR modifies runtime message identity and projection behavior in orchestration. Additionally, there are unresolved review comments identifying a potential crash bug in mergeThreadMessageProjection when processing first empty messages, which warrants human review before merging. You can customize Macroscope's approvability policy. Learn more. |


Summary
segment:0,segment:1, …) across turns, so T3 projected later replies into the samemessage_idas turn 1 and kept the originalcreated_at, sorting them above newer user messages.assistant:{turnId}:{baseKey}) and adds merge logic that resets text/created_atwhen a reused ID starts a new turn.Changes
assistantMessageIds.ts— turn-scoped assistant message ID generationthreadMessageProjection.ts— shared merge helper for projection upsertsProviderRuntimeIngestion.ts— use turn-scoped IDs for segments, completions, and diff checkpointsProjectionPipeline.ts/projector.ts— apply merge helper onthread.message-sentTest plan
assistantMessageIdsandthreadMessageProjectionProviderRuntimeIngestionexpectations for turn-scoped IDsassistant:session-1:segment:0across two turns creates distinct messages with correct timestampsapps/serverorchestration testsMade with Cursor
Note
Fix follow-up assistant replies not appearing after Cursor session resume
assistantSegmentMessageId, preventing collisions when providers reuse item IDs across turns in resumed sessions.mergeThreadMessageProjectionin threadMessageProjection.ts to centralize text-merge logic: resets text andcreatedAton turn change, appends streaming deltas only within the same turn.thread.message-sentevents that reuse a message ID in a new turn now reset the projected text andcreatedAtrather than appending to the previous turn's content.📊 Macroscope summarized 3a401bf. 5 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.