fix(gateway): prevent third-sender message drop in queue-mode debounce - #31496
Open
AhmetArif0 wants to merge 1 commit into
Open
fix(gateway): prevent third-sender message drop in queue-mode debounce#31496AhmetArif0 wants to merge 1 commit into
AhmetArif0 wants to merge 1 commit into
Conversation
When busy_text_mode=queue and a debounce state for sender B is stuck in the store (timer fired but the pending slot belongs to sender A, a different sender), any message from a third sender C hit an early `return` without being saved to the debounce store, pending_messages, or anywhere else — permanently and silently dropped. Remove the `_can_merge(existing_pending, event)` guard from the inner conflict branch in `_queue_text_debounce` and unconditionally call `merge_pending_message_event` instead. C's text is appended to the pending slot (or placed there directly if empty), matching the pre-debounce behaviour that always queued follow-ups regardless of sender. Sender attribution is best-effort when 3+ senders collide simultaneously; losing the message entirely is worse. Adds a regression test that pins the 3-sender scenario: A in pending, B stuck in the debounce store with task=None, C from a third sender — C must survive in _pending_messages and B must remain in the store for _drain_pending_after_session_command to recover.
teknium1
reviewed
Jul 13, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for tracing the shared-session collision; current main still has the early return at gateway/platforms/base.py:4262-4277, so the delivery-loss premise is valid for queue-mode shared sessions.
Problems
- The new unconditional merge at
gateway/platforms/base.py:2830appends C's text to the existing A event.merge_pending_message_event(..., merge_text=True)only updatesexisting.text(gateway/platforms/base.py:2137-2144), so C is processed under A's source. That conflicts with the existing separate-sender contract intests/gateway/test_active_session_text_merge.py:245-271. - The new test only checks that C text appears in the pending slot. It does not verify original event/source attribution or eventual separate delivery for B and C.
Suggested changes
- Preserve the events as separate queue entries instead of merging different senders. The runner's existing head-slot/overflow FIFO pattern is documented at
gateway/run.py:4365-4425. - Add a drain-path regression asserting A, B, and C are processed separately with their original sources.
Automated hermes-sweeper review.
| # same safety net as the pre-debounce path that always queued | ||
| # follow-ups. Sender attribution is best-effort when 3+ senders | ||
| # collide simultaneously; losing the message entirely is worse. | ||
| merge_pending_message_event( |
Contributor
There was a problem hiding this comment.
This merges C into A's existing MessageEvent. With merge_text=True, the helper only mutates existing.text and retains A's source (gateway/platforms/base.py:2137-2144), so C is misattributed. Please enqueue a distinct event instead of merging different senders.
This was referenced Jul 28, 2026
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.
Summary
busy_text_mode=queue(the default), messages from a third sender in a group chat are permanently and silently dropped when two prior senders have created a debounce/pending collision._queue_text_debouncehit an earlyreturnwithout saving the incoming event anywhere when (a) sender B's debounce state is stuck in the store (task=None, timer fired but couldn't flush because pending belongs to sender A) and (b) the new message from sender C can't merge with B's stuck state OR with pending-A._can_merge(existing_pending, event)guard from the inner conflict branch and unconditionally callmerge_pending_message_event— C's text is appended to the pending slot (or placed there directly if empty), matching the pre-debounce behaviour that always queued follow-ups regardless of sender.Affected path
gateway/platforms/base.py→_queue_text_debounce(), the innerif state is not None and not self._can_merge_text_debounce_events(state.event, event):branch introduced in #31341.Reproduction scenario
_pending_messages._flush_text_debounce_nowreturnsFalsebecause pending-A is a different sender → B stuck in store withtask=None, no retry scheduled._queue_text_debouncetries to flush B, fails again → hitsreturn→ C is never written to store, pending, or anywhere else.Only affects multi-user group chats (Matrix, Slack channels, Discord servers, WeChat groups) with 3+ participants messaging simultaneously during an active session.
Changes
gateway/platforms/base.pymerge_pending_message_eventin the stuck-debounce fallbacktests/gateway/test_active_session_text_merge.pytest_third_sender_not_dropped_when_debounce_store_is_stuckregression testTest plan
test_third_sender_not_dropped_when_debounce_store_is_stuckpins the exact 3-sender scenario (A in pending, B stuck in store withtask=None, C from third sender → C survives in_pending_messages)test_active_session_text_merge.pypasstest_busy_session_ack.py(16 tests),test_base_topic_sessions.py(10 tests),test_wecom.py(44 tests) — all pass, no regressions