🤖 fix: avoid replay/live delta duplication on reconnect#2165
🤖 fix: avoid replay/live delta duplication on reconnect#2165ethanndickson wants to merge 5 commits intomainfrom
Conversation
Buffer live stream/reasoning deltas during workspace.onChat replay and flush after caught-up, skipping deltas already emitted by replayStream().\n\nAlso emit parts before adding them to streamInfo.parts to avoid replay snapshotting in-flight parts. ---\n\n_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: `2.69`_\n\n<!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=12.69 -->
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad84e0c995
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Buffer stream-end/stream-abort/stream-error alongside deltas during replay so causal ordering is preserved. - Use counted replay keys and strictly monotonic per-stream timestamps to avoid delta identity collisions. --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: `$16.50`_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=16.50 -->
|
@codex review Addressed P1/P2:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ed764b95d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex follow-up: replay dedupe keys include delta text and don't need to be retained after replay finishes. Clear them to avoid per-connection memory growth. --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: `$16.50`_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=16.50 -->
|
@codex review Follow-up: cleared replay dedupe state after flush to avoid retaining delta text for the lifetime of the subscription. |
|
Codex Review: Didn't find any major issues. You're on a roll. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
No behavior change; just moves the replay buffering/dedupe logic into a small helper so the workspace.onChat handler stays linear/readable. --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: `$18.48`_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=18.48 -->
|
@codex review Refactor-only: extracted the onChat replay buffering/dedupe logic into a helper in router.ts to keep the handler linear/readable. |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Extract createReplayBufferedStreamMessageRelay into src/node/orpc/replayBufferedStreamMessageRelay.ts to keep router.onChat minimal. --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `medium` • Cost: `$20.58`_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=medium costs=20.58 -->
Summary
Prevent duplicated streamed deltas (including reasoning) on renderer reconnects, avoiding repeated-prefix UI artifacts (e.g.
EvaluEvaluEvalu...) and double token counting.Background
workspace.onChatsubscribes to live session events before awaitingreplayHistory(). If a renderer reconnects mid-stream, livestream-delta/reasoning-deltaevents can arrive while replay is in progress and then be emitted again byreplayStream()withreplay: true. The frontend aggregator is append-only, so duplicated delivery becomes repeated prefixes.Implementation
Backend (
src/node/orpc/router.ts)caught-up, skipping any deltas already delivered by replay (dedupe key:type + messageId + timestamp + delta).stream-end/stream-abort/stream-error) alongside deltas to preserve causal ordering.src/node/orpc/replayBufferedStreamMessageRelay.tsto keep theonChathandler minimal.Backend (
src/node/services/streamManager.ts)replayStream()(avoids replay snapshotting an “in-flight” part and then also receiving the later live emit).Validation
make static-checkRisks
(type, messageId, timestamp, delta), it will be skipped; monotonic timestamps reduce the chance of false positives.Generated with
mux• Model:openai:gpt-5.2• Thinking:medium• Cost:$20.78