hermes - ✅(Solved) Fix [Bug]: Google Chat replies land in the wrong thread under near-simultaneous mentions in the same space [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#24964Fetched 2026-05-14 03:50:17
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Timeline (top)
labeled ×5commented ×1cross-referenced ×1

Root Cause

In a Google Chat space (not a DM), when the bot is @-mentioned in two different threads within roughly the same second, the bot's outbound reply for one of those mentions occasionally posts into the other thread. The user sees their question answered, but in the wrong place — and any follow-up in the originating thread is broken because the reply never landed there.

Fix Action

Fixed

PR fix notes

PR #24997: fix(gateway): stop Google Chat cross-thread replies under concurrent same-space mentions

Description (problem / solution / changelog)

Scopes Google Chat adapter thread state per-thread so concurrent @-mentions in different threads of the same space stop crossing replies between threads (#24964).

What changed and why

  • Drop the _last_inbound_thread[chat_id] write in the group branch of _build_message_event. For groups, source.thread_id already carries the inbound thread name onto the MessageEvent, and the gateway plumbs it into outbound metadata via _thread_metadata_for_source — so the cache is redundant. Under near-simultaneous mentions in different threads of the same space, the single-slot cache races and the slower outbound's _resolve_thread_id fallback would otherwise return whichever thread won the cache-write race. Skipping the cache makes the fallback return None (top-level reply) for groups when metadata is missing, which is still wrong but visibly broken rather than silently cross-threaded.
  • Add a _typing_card_threads: Dict[str, Optional[str]] sidecar recording the thread each _typing_messages[chat_id] card was created in. send(), on_processing_complete, and _consume_typing_card_with_text now compare the recorded card thread against the current session's outbound thread and refuse to patch on mismatch. messages.patch cannot change a message's thread, so patching a sibling's card would have silently dropped the reply text into the sibling's thread; the guard falls through to _create_message in the correct thread instead and leaves the sibling's card in the slot for its own send() / cleanup to finalize.
  • DM behavior is unchanged: _last_inbound_thread is still populated for DM side-threads, the DM main-flow vs side-thread heuristic still drives session isolation, and the anti-tombstone typing-card lifecycle still works.

How to test

  • pytest tests/gateway/test_google_chat.py -q — all 161 tests pass, including four new regression tests in TestConcurrentSpaceThreadRouting covering: (1) groups no longer leak threads into the single-slot cache, (2) two different threads in the same space don't overwrite each other, (3) send() refuses to patch a sibling-thread typing card and creates a new message in its own thread, (4) on_processing_complete leaves a sibling-thread card in place.
  • ruff check plugins/platforms/google_chat/adapter.py tests/gateway/test_google_chat.py — clean.

What platforms tested on

  • macOS on darwin-arm64 (local pytest run)

Fixes #24964

<!-- autocontrib:worker-id=issue-new-3ad1f0f5 kind=pr-open -->

Changed files

  • plugins/platforms/google_chat/adapter.py (modified, +78/-12)
  • tests/gateway/test_google_chat.py (modified, +117/-0)

Code Example

# adapter.py ~L507
# Last-seen inbound thread name per chat_id (space).
self._last_inbound_thread: Dict[str, str] = {}

---

if chat_id:
    cached = self._last_inbound_thread.get(chat_id)
    if cached:
        return cached
RAW_BUFFERClick to expand / collapse

Bug Description

In a Google Chat space (not a DM), when the bot is @-mentioned in two different threads within roughly the same second, the bot's outbound reply for one of those mentions occasionally posts into the other thread. The user sees their question answered, but in the wrong place — and any follow-up in the originating thread is broken because the reply never landed there.

It's intermittent (race-conditioned), so it doesn't reproduce on every attempt, but it's very repeatable under bursty traffic in a busy space.

Steps to Reproduce

  1. Add the bot to a Google Chat space that already has at least two active threads (Thread A and Thread B).
  2. From Thread A, @bot ask a question that takes a couple of seconds to answer (e.g. anything that triggers a tool call, a model call with a non-trivial prompt, or image generation — anything where the inbound→outbound latency is >~500 ms).
  3. Within ~1 second, from Thread B, @bot send a different question.
  4. Repeat a few times. On some attempts, the reply intended for Thread A is posted into Thread B (or vice-versa). Occasionally a reply lands at the space top-level instead of in either thread.

Expected Behavior

Each outbound reply lands in the same thread as the inbound mention that triggered it, regardless of how closely other mentions arrive in the same space.

Actual Behavior

Replies get crossed between threads under concurrent inbound mentions in the same space. Sometimes a reply lands at top-level instead of in any thread.

Likely cause (from a read of plugins/platforms/google_chat/adapter.py)

The adapter caches the most recent inbound thread per space in a single-slot dict:

# adapter.py ~L507
# Last-seen inbound thread name per chat_id (space).
self._last_inbound_thread: Dict[str, str] = {}

_resolve_thread_id (≈L2086) falls back to that cache when metadata['thread_id'] isn't carried through on the outbound side:

if chat_id:
    cached = self._last_inbound_thread.get(chat_id)
    if cached:
        return cached

Because the cache is keyed by chat_id only (not by the originating message / session / thread), two near-simultaneous inbound messages in the same space race: the second inbound overwrites _last_inbound_thread[chat_id] before the slower outbound for the first inbound resolves its thread → the slower reply is routed into the second message's thread.

_ThreadCountStore is keyed by (chat_id, thread_name), which is fine in isolation, but its "main-flow vs side-thread" heuristic (the prev_count == 0 branch, ~L324) treats any thread we haven't seen yet as "Google Chat auto-created this thread for the user — reply at top-level". Under the burst, the heuristic can fire for whichever thread loses the race and demote a real side-thread reply to top-level. That matches the occasional "reply lands at space top-level" symptom above.

Suggested fix

Outbound thread routing should be derived from per-message state captured at inbound time, not from mutable, single-slot adapter state.

Concretely:

  • Carry the inbound thread.name on the message context object itself (e.g. on the gateway's session source / event metadata) all the way through to the send path, and have _resolve_thread_id read it from there.
  • Drop, or at minimum scope, _last_inbound_thread. If a fallback is still needed for DMs (where thread_id is intentionally stripped to keep session_key stable), key it by (chat_id, session_id) or — better — by the originating message id, not by chat_id alone.
  • Same for the _ThreadCountStore side-thread heuristic: the "first time we see this thread" branch is a per-thread historical fact and shouldn't be allowed to flip based on inter-message timing in the same space.

Affected Component

  • Gateway (Google Chat platform adapter — plugins/platforms/google_chat/adapter.py)

Messaging Platform

  • Google Chat (not in the bug-report dropdown — flagging here)

Environment

  • I will attach hermes debug share links here when I can reliably reproduce on the latest version. Filing now so the analysis isn't lost; happy to follow up with logs.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug]: Google Chat replies land in the wrong thread under near-simultaneous mentions in the same space [1 pull requests, 1 comments, 2 participants]