openclaw - 💡(How to fix) Fix Google Chat outbound: thread/reply routing metadata leaks across streamed block replies

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…

In the googlechat outbound channel, chunks of a single reply can land outside the thread of the inbound message (or split between thread and top-level of the space). The pipeline preserves the chat destination but not the thread target across chunks.

Affects: v2026.4.12 (likely earlier). Reproducible in DMs and spaces with threaded replies. Observed in production across three separate googlechat accounts in one deployment (same box, distinct accounts.* entries).

Root Cause

Root cause (evidence)

Fix Action

Workaround

Setting blockStreaming: false on affected accounts disables the coalescer and eliminates most cross-thread leakage — each reply goes as a single send carrying its own replyToId. Final-chunk continuation leakage can still occur via edit/append flows, but the symptom frequency drops dramatically. Trade-off: UX loses the streaming-typing experience.

Code Example

export type OutboundReplyPayload = {
    text?: string;
    mediaUrls?: string[];
    mediaUrl?: string;
    replyToId?: string;   // no threadKey / thread.name
};

---

return JSON.stringify({ text, mediaList, replyToId: payload.replyToId ?? null });

---

const replyToConflict = Boolean(
      bufferText && payload.replyToId &&
      (!bufferReplyToId || bufferReplyToId !== payload.replyToId)
    );

---

/** Thread id for reply routing (Telegram topic id or Matrix thread event id). */
originatingThreadId?: string | number;
RAW_BUFFERClick to expand / collapse

Summary

In the googlechat outbound channel, chunks of a single reply can land outside the thread of the inbound message (or split between thread and top-level of the space). The pipeline preserves the chat destination but not the thread target across chunks.

Affects: v2026.4.12 (likely earlier). Reproducible in DMs and spaces with threaded replies. Observed in production across three separate googlechat accounts in one deployment (same box, distinct accounts.* entries).

Symptoms

  • Case A: typingIndicator: "message" shows the typing inside the thread; final answer posts to the space top-level.
  • Case B: first chunk lands in the thread; continuation/append chunks land on the space top-level.
  • Queued follow-ups originating from a thread drain back to top-level.

Root cause (evidence)

Thread is not a first-class field of the outbound payload or the coalescer's identity. It is never carried forward between chunks of the same reply.

1. OutboundReplyPayload has no thread fielddist/plugin-sdk/src/plugin-sdk/reply-payload.d.ts:5-10:

export type OutboundReplyPayload = {
    text?: string;
    mediaUrls?: string[];
    mediaUrl?: string;
    replyToId?: string;   // no threadKey / thread.name
};

Google Chat's REST API exposes thread.name / threadKey separately from message-level reply. Without either, spaces.messages.create defaults to a new top-level message in the space.

2. Coalescer keys and conflict detection ignore threaddist/block-reply-pipeline-BG5Rf5FK.js:

  • L136-143 createBlockReplyPayloadKey:
    return JSON.stringify({ text, mediaList, replyToId: payload.replyToId ?? null });
  • L79 replyToConflict:
    const replyToConflict = Boolean(
      bufferText && payload.replyToId &&
      (!bufferReplyToId || bufferReplyToId !== payload.replyToId)
    );

A chunk arriving with replyToId=undefined is silently merged into a buffer whose intended thread may differ — on flush, the resulting send has no thread hint and defaults to top-level.

3. Follow-up routing type enumerates Telegram/Matrix onlydist/plugin-sdk/src/auto-reply/reply/queue/types.d.ts:36-37:

/** Thread id for reply routing (Telegram topic id or Matrix thread event id). */
originatingThreadId?: string | number;

Google Chat thread is not plumbed through FollowupRun, so queued/drained replies from a thread cannot be restored to their source thread.

Reproduction

Config: typingIndicator: "message", blockStreaming: true (default).

  • Case A: send a message inside a thread in a space where the bot is mentioned. Observe typing indicator in-thread; final answer posts top-level.
  • Case B: send a message long enough to cross textChunkLimit (4000 chars default). First chunk in-thread, subsequent chunks top-level.

Suggested fix

  1. Extend OutboundReplyPayload with thread metadata (threadName, threadKey) and spaceId where relevant. Populate at inbound webhook ingress from the Google Chat event payload.
  2. Include these fields in createBlockReplyPayloadKey / createBlockReplyContentKey so coalescing and dedup are thread-aware.
  3. Extend replyToConflict to also trigger on thread mismatch, not only replyToId mismatch.
  4. Pass thread through to the googlechat adapter's sendPayload, which should set thread.name / threadKey on every spaces.messages.create call within the reply lifecycle (initial + chunk + append + edit + final flush).
  5. Plumb thread through FollowupRun.originatingThreadId for Google Chat — update the docstring and add dispatcher logic to restore thread on queue drain.
  6. Hard stop: if a reply started in-thread and thread info is missing on a later chunk / edit / append / final flush, abort with an explicit log rather than silently sending top-level.

Workaround

Setting blockStreaming: false on affected accounts disables the coalescer and eliminates most cross-thread leakage — each reply goes as a single send carrying its own replyToId. Final-chunk continuation leakage can still occur via edit/append flows, but the symptom frequency drops dramatically. Trade-off: UX loses the streaming-typing experience.

Environment

  • openclaw v2026.4.12
  • Node 22 on Ubuntu 25.10
  • 3 googlechat accounts with audienceType: "app-url", service-account auth, typingIndicator: "message"

Credit

Original technical report authored by Igor Almeida (internal ops, downstream deployment). This issue re-frames that report against the current bundle source with specific file/line references for maintainer review.

extent analysis

TL;DR

To fix the issue of chunks of a single reply landing outside the thread of the inbound message in the googlechat outbound channel, extend the OutboundReplyPayload with thread metadata and update the coalescer to be thread-aware.

Guidance

  • Extend OutboundReplyPayload with threadName and threadKey fields to preserve thread information across chunks.
  • Update createBlockReplyPayloadKey and createBlockReplyContentKey to include thread metadata for thread-aware coalescing and deduplication.
  • Modify replyToConflict to trigger on thread mismatch, not just replyToId mismatch, to prevent silent merging of chunks into the wrong thread.
  • Pass thread information to the googlechat adapter's sendPayload to set thread.name / threadKey on every spaces.messages.create call.
  • Update FollowupRun.originatingThreadId to plumb thread information through for Google Chat and restore thread on queue drain.

Example

export type OutboundReplyPayload = {
  text?: string;
  mediaUrls?: string[];
  mediaUrl?: string;
  replyToId?: string;
  threadName?: string; // added thread metadata
  threadKey?: string; // added thread metadata
};

Notes

The suggested fix requires updates to multiple components, including the OutboundReplyPayload, coalescer, and googlechat adapter. The workaround of setting blockStreaming: false can mitigate the issue but may impact the user experience.

Recommendation

Apply the suggested fix to extend OutboundReplyPayload with thread metadata and update the coalescer to be thread-aware, as this will provide a more comprehensive solution to the issue.

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

openclaw - 💡(How to fix) Fix Google Chat outbound: thread/reply routing metadata leaks across streamed block replies