Repository navigation
fix(streaming): drop silent delta-discard in _merge_stream_text - #4085
Merged
WillemJiang merged 1 commit intoJul 12, 2026
Merged
Conversation
willem-bd
reviewed
Jul 11, 2026
willem-bd
left a comment
Contributor
There was a problem hiding this comment.
Thanks for this fix. The view_state.py change is correct, but I found one issue in the manager.py path that breaks the PR's own tests.
| if len(chunk) > len(existing) and chunk.startswith(existing): | ||
| return chunk | ||
| # Exact re-send (values snapshot re-emitting history): no-op. | ||
| if len(chunk) == len(existing) and chunk == existing: |
Contributor
There was a problem hiding this comment.
[P1] Exact-match no-op check drops legitimate equal-chunk deltas — the PR's own tests fail
- Location:
backend/app/channels/manager.py:379(_merge_stream_text) - Problem: The added exact-match check
if len(chunk) == len(existing) and chunk == existing: return existingfires before the delta-append at line 384. For an incoming delta that happens to equal the accumulated buffer (CJK reduplication'谢' + '谢', repeated tokens'go' + 'go', split newlines'\n' + '\n'), it returnsexistingunchanged, discarding the delta. The PR's own added comment at lines 381-383 states this case should append ('谢' + '谢' = '谢谢'), and the new tests intest_channels.pyassert it — but they fail against this implementation. - Evidence:
_get_merge_stream_text()imports_merge_stream_textfromapp.channels.manager(test_channels.py:7320), andtest_merge_stream_text_cjk_reduplicationasserts_merge("谢", "谢") == "谢谢"(test_channels.py:7328). Tracing the head code: for("谢","谢"),len(chunk) == len(existing)is1 == 1→ True andchunk == existing→ True, so line 380 returns"谢"instead of"谢谢". The same breakstest_merge_stream_text_repeated_token_append("go"+"go"→ returns"go") andtest_merge_stream_text_newline_split("\n"+"\n"→ returns"\n"). Theview_state.pyversion was fixed correctly (no exact-match check);manager.pywas not. - Suggested fix: Remove the exact-match check (lines 378-380) from
manager.py's_merge_stream_textto matchview_state.py. The channels path feeds only delta (messages-tuple) events to this function —valuessnapshots are handled in the separateelif event == "values"branch at line 1732 — so the no-op guard serves no purpose here and only drops legitimate equal-chunk deltas. The strictly-longer cumulative check at line 376 already handles true cumulative re-delivery. - Test: The three tests above (
test_merge_stream_text_cjk_reduplication,test_merge_stream_text_repeated_token_append,test_merge_stream_text_newline_split) already cover this — they need to pass.cd backend && make test.
The dual-mode _merge_stream_text used short-circuits chunk==existing and existing.endswith(chunk) that silently dropped legitimate delta chunks in messages-tuple mode: - CJK reduplication: '谢谢' tokenized as ['谢','谢'] -> buffer stays '谢' - Repeated tokens: 'gogo' as ['go','go'] -> buffer stays 'go' - Suffix-matching tails: 'hello' as ['hel','l','o'] -> drops 'l' Delta payloads must always append; only strictly-longer cumulative snapshots should replace. The fix gates the cumulative-replace branch with len(chunk) > len(existing) and removes the dropped-shape guards. Both copies are fixed: - app/channels/manager.py (IM streaming to Feishu/Telegram/etc.) - deerflow/tui/view_state.py (TUI client rendering) The TUI reduce() caller now handles exact re-sends (values snapshot re-emitting history) before the merge, and the len>1 guard prevents single-char CJK deltas from being mistaken for re-sends. Co-Authored-By: Claude <noreply@anthropic.com>
nankingjing
force-pushed
the
fix/delta-stream-accumulation-cjk
branch
from
July 12, 2026 01:46
b778bd2 to
8d007e4
Compare
Contributor
Author
|
Thanks for the careful review! Fixed in commit 8d007e4 — removed the exact-match no-op at manager.py:378-380 so channels/manager.py now matches view_state.py. All 8 tests in test_channels.py (test_merge_stream_text_*) pass locally with this change. |
WillemJiang
approved these changes
Jul 12, 2026
Merged
6 of 9 tasks
lllyfff
added a commit
to qin-chenghan/deer-flow
that referenced
this pull request
Jul 13, 2026
Brings in 4fd521e..2bd0f56: sandbox fixes (bytedance#4051/bytedance#4053/bytedance#4058/bytedance#4078/bytedance#4079/bytedance#4110), CVE-2026-33128/bytedance#4089/bytedance#4093/bytedance#4106, subagents (bytedance#4056/bytedance#4059/bytedance#4088), skillscan (bytedance#4087), channels (bytedance#4055/bytedance#4069/bytedance#4100), stream_bridge (bytedance#4071), frontend (bytedance#4058/bytedance#4068/bytedance#4094/ bytedance#4114/bytedance#4126), event-store serialization (bytedance#4077), streaming (bytedance#4082/bytedance#4085), mcp (bytedance#4084), guardrails (bytedance#4067), and the memory fixes bytedance#4073/bytedance#4074/bytedance#4075/bytedance#4076/bytedance#4119. Memory conflict resolution (upstream is still monolithic agents/memory/, we are vendored backends/deermem/deermem/core/ - git rename-detected host->vendored): - core/prompt.py, core/updater.py: keep ours (vendored with all ports); upstream's host-side bytedance#4119/bytedance#4074/bytedance#4075/bytedance#4076 changes are the same fixes already ported. - core/queue.py: auto-merged cleanly (both sides had identical bytedance#4073 change). - test_memory_queue.py: replaced the stale pre-bytedance#4073 test with upstream's three bytedance#4073 tests, adapted from host API (MemoryUpdateQueue()) to vendored (_queue()). - test_memory_search.py: kept our vendored tests + added a vendored test_null_confidence_does_not_crash_sort (bytedance#4076 coverage). Post-merge: 171 memory tests pass (+2 module-skipped), 7164 collected, no import/collection errors. All 8 upstream memory fixes since the 8be7411 divergence are adapted (bytedance#4044/bytedance#4028/bytedance#4097/bytedance#4119/bytedance#4073/bytedance#4074/bytedance#4075/bytedance#4076). Co-Authored-By: Claude <noreply@anthropic.com>
8 tasks
marvin9551
pushed a commit
to marvin9551/deer-flow
that referenced
this pull request
Aug 21, 2026
…dance#4085) The dual-mode _merge_stream_text used short-circuits chunk==existing and existing.endswith(chunk) that silently dropped legitimate delta chunks in messages-tuple mode: - CJK reduplication: '谢谢' tokenized as ['谢','谢'] -> buffer stays '谢' - Repeated tokens: 'gogo' as ['go','go'] -> buffer stays 'go' - Suffix-matching tails: 'hello' as ['hel','l','o'] -> drops 'l' Delta payloads must always append; only strictly-longer cumulative snapshots should replace. The fix gates the cumulative-replace branch with len(chunk) > len(existing) and removes the dropped-shape guards. Both copies are fixed: - app/channels/manager.py (IM streaming to Feishu/Telegram/etc.) - deerflow/tui/view_state.py (TUI client rendering) The TUI reduce() caller now handles exact re-sends (values snapshot re-emitting history) before the merge, and the len>1 guard prevents single-char CJK deltas from being mistaken for re-sends. Co-authored-by: Claude <noreply@anthropic.com>
jihtsan
pushed a commit
to jihtsan/dnx-deer-flow
that referenced
this pull request
Aug 29, 2026
…dance#4085) The dual-mode _merge_stream_text used short-circuits chunk==existing and existing.endswith(chunk) that silently dropped legitimate delta chunks in messages-tuple mode: - CJK reduplication: '谢谢' tokenized as ['谢','谢'] -> buffer stays '谢' - Repeated tokens: 'gogo' as ['go','go'] -> buffer stays 'go' - Suffix-matching tails: 'hello' as ['hel','l','o'] -> drops 'l' Delta payloads must always append; only strictly-longer cumulative snapshots should replace. The fix gates the cumulative-replace branch with len(chunk) > len(existing) and removes the dropped-shape guards. Both copies are fixed: - app/channels/manager.py (IM streaming to Feishu/Telegram/etc.) - deerflow/tui/view_state.py (TUI client rendering) The TUI reduce() caller now handles exact re-sends (values snapshot re-emitting history) before the merge, and the len>1 guard prevents single-char CJK deltas from being mistaken for re-sends. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The dual-mode
_merge_stream_textused short-circuitschunk == existingandexisting.endswith(chunk)that silently dropped legitimate delta chunks in messages-tuple mode:Delta payloads must always append; only strictly-longer cumulative snapshots should replace.
Fix
Both copies of
_merge_stream_textare fixed:app/channels/manager.py(IM streaming to Feishu/Telegram/etc.): Gates cumulative-replace withlen(chunk) > len(existing), keeps exact-resend guard for values-snapshot no-ops.deerflow/tui/view_state.py(TUI client rendering): Simplified to always-append delta logic; exact-re-send detection moved to callerreduce()withlen(action.text) > 1guard so single-char CJK deltas aren't mistaken for re-sends.Tests
test_tui_view_state.py: +8 regression tests (CJK reduplication, repeated tokens, suffix-matching, cumulative, empty, newline split, genuine append)test_channels.py: +8 equivalent regression testsAll existing tests continue to pass (27/27 TUI, 258/258 channels).
🤖 Generated with Claude Code