Skip to content

fix(telegram): preserve forwarded message metadata during debounce batching#8368

Open
PatrickBauer wants to merge 1 commit intoopenclaw:mainfrom
PatrickBauer:fix/forward-metadata-debounce-batching
Open

fix(telegram): preserve forwarded message metadata during debounce batching#8368
PatrickBauer wants to merge 1 commit intoopenclaw:mainfrom
PatrickBauer:fix/forward-metadata-debounce-batching

Conversation

@PatrickBauer
Copy link

@PatrickBauer PatrickBauer commented Feb 3, 2026

Problem

When multiple Telegram messages arrive within the messages.inbound.debounceMs window and get batched, forward metadata (forward_origin, forward_from, forward_from_chat, etc.) is lost for all messages except the first one.

Root Cause

In bot-handlers.ts, the onFlush callback builds a syntheticMessage by spreading only first.msg, so forward metadata from entries[1..n] is discarded.

Fix

  • Extract shared buildForwardPrefix() helper into bot/helpers.ts — avoids format duplication between bot-message-context.ts and bot-handlers.ts
  • Inline forward attribution per entry: Before joining texts, each entry is checked for forward metadata via normalizeForwardedContext(). If present, a [Forwarded from ...] prefix is prepended (including forwarded messages without text/caption).
  • Strip forward fields from synthetic message: Forward fields are destructured out of first.msg to prevent bot-message-context.ts from double-prefixing the first entry.

Testing

Forward 3+ messages from different senders quickly (within debounce window). Before this fix, only the first message retained its [Forwarded from ...] header. After the fix, all messages preserve their attribution.

Relates to #6263

Greptile Overview

Greptile Summary

This PR fixes Telegram inbound debounce batching so forwarded-message attribution is preserved for each message in a batch instead of only the first one. It does this by extracting a shared buildForwardPrefix() helper into src/telegram/bot/helpers.ts, using normalizeForwardedContext() per entry when composing the batched combinedText, and stripping forward_* fields from the synthetic message to avoid double-prefixing when the synthetic message is later processed by buildTelegramMessageContext().

These changes fit into the existing Telegram pipeline by keeping the synthetic-message approach (batched messages are still processed as a single TelegramMessage), while ensuring the user-visible forward attribution survives batching.

Confidence Score: 4/5

  • This PR is likely safe to merge and should fix the reported forwarded-metadata loss in debounced Telegram batches.
  • Changes are narrowly scoped to text composition and forward-prefix formatting, and they intentionally strip forward fields from the synthetic message to avoid double-prefixing. Remaining concerns are mainly about helper ergonomics (newline handling) and ensuring debounce batching invariants hold.
  • src/telegram/bot-handlers.ts; src/telegram/bot/helpers.ts

@openclaw-barnacle openclaw-barnacle bot added the channel: telegram Channel integration: telegram label Feb 3, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@PatrickBauer PatrickBauer force-pushed the fix/forward-metadata-debounce-batching branch from 22c7b0a to f07cd40 Compare February 3, 2026 23:37
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +452 to +454
export function buildForwardPrefix(fwd: TelegramForwardedContext): string {
return `[Forwarded from ${fwd.from}${fwd.date ? ` at ${new Date(fwd.date * 1000).toISOString()}` : ""}]\n`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] buildForwardPrefix() returning a trailing newline makes it easy to misuse

buildForwardPrefix() always appends \n, which forces call sites to remember to trimEnd() when they want a standalone prefix (as in debounce batching). That’s already bitten this PR once and makes future call sites prone to accidental extra blank lines. Consider returning the prefix without a trailing newline and letting callers add separators explicitly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot/helpers.ts
Line: 452:454

Comment:
[P2] `buildForwardPrefix()` returning a trailing newline makes it easy to misuse

`buildForwardPrefix()` always appends `\n`, which forces call sites to remember to `trimEnd()` when they want a standalone prefix (as in debounce batching). That’s already bitten this PR once and makes future call sites prone to accidental extra blank lines. Consider returning the prefix *without* a trailing newline and letting callers add separators explicitly.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai Fair point. However, changing buildForwardPrefix() to not include the trailing newline would be a behavioral change for the existing call site in bot-message-context.ts where the newline is expected (it gets concatenated directly with the body text). Since this PR is scoped to fixing the debounce batching bug, I kept the helper consistent with its existing behavior and handled the edge case at the call site with .trimEnd(). A follow-up to normalize this would make sense though — happy to do that as a separate PR if the maintainers prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that changing the behavior mid-PR would expand the scope. The .trimEnd() workaround is fine for now — it's explicit about handling the edge case and keeps this PR focused on the debounce fix.

If you do a follow-up, consider that the current bot-message-context.ts usage concatenates prefix + body, so returning no newline and letting callers write buildForwardPrefix(fwd) + '\n' + body would make the separator explicit everywhere. But that's a refactor for another day.

For this PR, the approach is reasonable and the comment explains the tradeoff. ✅

Comment on lines 94 to +101
const combinedText = entries
.map((entry) => entry.msg.text ?? entry.msg.caption ?? "")
.map((entry) => {
const text = entry.msg.text ?? entry.msg.caption ?? "";
const fwd = normalizeForwardedContext(entry.msg);
if (fwd) {
const prefix = buildForwardPrefix(fwd).trimEnd();
return text ? `${prefix}\n${text}` : prefix;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Consider asserting batch invariants when adding per-entry forward attribution

This batching still builds the synthetic message from first.msg (minus forward fields) while combinedText now embeds per-entry forward attribution. If debounceKey ever groups messages across different conversations/senders (buggy keying or unexpected ctx fields), the resulting combined text could contain misleading forward headers in a single synthetic message context. It may be worth adding a lightweight assertion/log that entries share the same chat/thread/sender IDs before composing combinedText (or at least in verbose logging).

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-handlers.ts
Line: 94:101

Comment:
[P2] Consider asserting batch invariants when adding per-entry forward attribution

This batching still builds the synthetic message from `first.msg` (minus forward fields) while `combinedText` now embeds per-entry forward attribution. If `debounceKey` ever groups messages across different conversations/senders (buggy keying or unexpected ctx fields), the resulting combined text could contain misleading forward headers in a single synthetic message context. It may be worth adding a lightweight assertion/log that `entries` share the same chat/thread/sender IDs before composing `combinedText` (or at least in verbose logging).

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai The debounceKey is built from chat ID + thread ID (see buildKey in the debouncer setup), so entries within a batch are already guaranteed to belong to the same conversation. Cross-chat mixing would be a bug in the debouncer itself, not in this code path.

That said, a verbose-level log confirming batch composition could be useful for debugging — but that feels out of scope for this bugfix PR. The existing onError handler already catches flush failures.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right—if debounceKey construction already enforces chat+thread identity, then batch invariants are guaranteed upstream and cross-chat mixing can't happen here.

A verbose log would help debugging, but you're also right that it's out of scope for this fix. The change is correct as-is.

I'm satisfied with the explanation. No action needed.

…tching

When multiple Telegram messages arrive within the debounceMs window and get
batched, forward metadata (forward_origin, forward_from, etc.) was lost for
all messages except the first one, since the synthetic message only spread
first.msg properties.

Changes:
- Extract shared buildForwardPrefix() helper into bot/helpers.ts
- Inline [Forwarded from ...] prefix per entry in combined text, including
  forwarded messages without text/caption
- Strip forward fields from synthetic message via destructuring to prevent
  double-prefixing by bot-message-context.ts

Relates to openclaw#6263
@openclaw-barnacle
Copy link

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added stale Marked as stale due to inactivity and removed stale Marked as stale due to inactivity labels Feb 21, 2026
@mudrii

This comment was marked as spam.

@openclaw-barnacle
Copy link

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants