Skip to content

fix: cross-message reasoning dedup to prevent OpenAI rs_* ID poisoning#804

Open
JasonOA888 wants to merge 2 commits into
vercel-labs:mainfrom
JasonOA888:fix/cross-message-reasoning-dedup
Open

fix: cross-message reasoning dedup to prevent OpenAI rs_* ID poisoning#804
JasonOA888 wants to merge 2 commits into
vercel-labs:mainfrom
JasonOA888:fix/cross-message-reasoning-dedup

Conversation

@JasonOA888
Copy link
Copy Markdown

Summary

Fixes #545 (immediate guardrail portion)

When a workflow stream is resumed/replayed, entire assistant messages can be duplicated with blank reasoning parts that carry the same rs_* item IDs as earlier messages. OpenAI requires item IDs to be unique across input history, so these duplicates cause follow-up turns to fail.

Problem

The existing dedupeMessageReasoning only deduplicates within a single message. But the replay bug produces cross-message duplicates — a new assistant message containing only blank reasoning parts with IDs that already appeared in a previous message.

Changes

dedupe-message-reasoning.ts

  • Added dedupeCrossMessageReasoning() — processes the full message array and:
    • Tracks rs_* item IDs across all messages in order
    • Removes blank reasoning-only assistant messages whose IDs were already seen (replay artifacts)
    • Strips individual blank-text reasoning parts from mixed messages
    • Preserves non-blank multi-summary segments (same ID, different text)
    • Returns the same array reference when no dedup is needed

chat.ts (workflow)

  • convertMessages now applies cross-message dedup as a second pass after the existing per-message dedup

dedupe-message-reasoning.test.ts

  • 10 new test cases covering: replay removal, new ID preservation, blank part stripping, multi-summary preservation, empty message removal, Azure metadata, immutability

Test results

17 pass, 0 fail (dedupe-message-reasoning.test.ts)
28 pass, 0 fail (chat.test.ts)

All existing tests continue to pass.

Scope

This PR addresses the immediate guardrail from #545 — preventing poisoned messages from reaching the OpenAI API. The root fix (chunk-index-aware workflow resume with startIndex) is a larger change that should be handled separately.

When a workflow stream is resumed or replayed, entire assistant messages
can be duplicated with blank reasoning parts that carry the same rs_* item
IDs as earlier messages. OpenAI requires item IDs to be unique across the
input history, so these duplicates cause follow-up turns to fail.

Add dedupeCrossMessageReasoning() which:
- Tracks rs_* item IDs across all messages in order
- Removes blank reasoning-only assistant messages whose IDs were already
  seen in an earlier message (replay artifacts)
- Strips individual blank-text reasoning parts from mixed messages while
  preserving non-blank multi-summary segments
- Returns the same array reference when no dedup is needed

Integrate into convertMessages as a second pass after the existing
per-message dedup (dedupeMessageReasoning).

Closes vercel-labs#545 (immediate guardrail portion)
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

@JasonOA888 is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread apps/web/lib/chat/dedupe-message-reasoning.ts Outdated
…metadata

Add messageIds.length > 0 check to prevent incorrectly dropping
reasoning-only assistant messages that lack rs_* item IDs.

Co-authored-by: VADE <vade@vercel.com>
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.

Workflow stream resume can replay OpenAI reasoning rs_* items and poison chat history

1 participant