Skip to content

fix(orchestration): show follow-up assistant replies after Cursor session resume#3642

Open
davidmashburn wants to merge 1 commit into
pingdotgg:mainfrom
davidmashburn:fix/cursor-resume-assistant-message-order
Open

fix(orchestration): show follow-up assistant replies after Cursor session resume#3642
davidmashburn wants to merge 1 commit into
pingdotgg:mainfrom
davidmashburn:fix/cursor-resume-assistant-message-order

Conversation

@davidmashburn

@davidmashburn davidmashburn commented Jul 2, 2026

Copy link
Copy Markdown

Summary

  • Fixes a thread UI bug where follow-up assistant replies appeared missing after resuming a Cursor session.
  • Root cause: ACP reuses assistant segment IDs (segment:0, segment:1, …) across turns, so T3 projected later replies into the same message_id as turn 1 and kept the original created_at, sorting them above newer user messages.
  • Scopes assistant message IDs by turn (assistant:{turnId}:{baseKey}) and adds merge logic that resets text/created_at when a reused ID starts a new turn.

Changes

  • assistantMessageIds.ts — turn-scoped assistant message ID generation
  • threadMessageProjection.ts — shared merge helper for projection upserts
  • ProviderRuntimeIngestion.ts — use turn-scoped IDs for segments, completions, and diff checkpoints
  • ProjectionPipeline.ts / projector.ts — apply merge helper on thread.message-sent
  • Tests including regression for reused Cursor segment IDs across turns

Test plan

  • Unit tests for assistantMessageIds and threadMessageProjection
  • Updated ProviderRuntimeIngestion expectations for turn-scoped IDs
  • Regression test: resumed session reusing assistant:session-1:segment:0 across two turns creates distinct messages with correct timestamps
  • CI green on apps/server orchestration tests

Made with Cursor


Open in Devin Review

Note

Fix follow-up assistant replies not appearing after Cursor session resume

  • Assistant message IDs are now turn-scoped via assistantSegmentMessageId, preventing collisions when providers reuse item IDs across turns in resumed sessions.
  • Introduces mergeThreadMessageProjection in threadMessageProjection.ts to centralize text-merge logic: resets text and createdAt on turn change, appends streaming deltas only within the same turn.
  • Both the projector in projector.ts and ProjectionPipeline.ts are updated to use this shared merge function.
  • Behavioral Change: thread.message-sent events that reuse a message ID in a new turn now reset the projected text and createdAt rather 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.

…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>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f3aba4cb-896b-449a-af99-b7461cc713e7

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

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3a401bf. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant