Skip to content

fix(telegram): add message deduplication to prevent duplicate processing #597

@youbiak

Description

@youbiak

Problem

Telegram messages can be redelivered after gateway restarts, getUpdates conflicts, or network reconnects. This causes the same message to be processed multiple times, spamming sessions with duplicates.

Evidence: A single "Hello" message (ID 1687) was delivered 10+ times to a session over 25 minutes.

Solution

Add seenMessages map (matching the pattern already used in Slack adapter - see src/slack/monitor.ts:501-512) to track chatId:messageId pairs and skip already-processed messages.

Patch

From 32caa8755ca000c439f79c5c1c083556604261b9 Mon Sep 17 00:00:00 2001
From: youbiak <x.x@gmail.com>
Date: Fri, 9 Jan 2026 18:30:35 +0000
Subject: [PATCH] fix(telegram): add message deduplication to prevent duplicate
 processing

Telegram messages can be redelivered after gateway restarts, getUpdates
conflicts, or network reconnects. This causes the same message to be
processed multiple times, spamming sessions with duplicates.

Add seenMessages map (matching the pattern used in Slack adapter) to
track chatId:messageId pairs and skip already-processed messages.

- Track up to 500 recent messages
- Auto-cleanup entries older than 60 seconds
- Log skipped duplicates in verbose mode
---
 src/telegram/bot.ts | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts
index 52461ef5..9b151598 100644
--- a/src/telegram/bot.ts
+++ b/src/telegram/bot.ts
@@ -172,6 +172,26 @@ export function createTelegramBot(opts: TelegramBotOptions) {
 
   const mediaGroupBuffer = new Map<string, MediaGroupEntry>();
 
+  // Message deduplication - prevents reprocessing messages after restarts/reconnects
+  const seenMessages = new Map<string, number>();
+  const isMessageSeen = (chatId: number, messageId: number): boolean => {
+    const key = `${chatId}:${messageId}`;
+    if (seenMessages.has(key)) return true;
+    seenMessages.set(key, Date.now());
+    // Cleanup old entries to prevent memory growth
+    if (seenMessages.size > 500) {
+      const cutoff = Date.now() - 60_000;
+      for (const [entry, seenAt] of seenMessages) {
+        if (seenAt < cutoff || seenMessages.size > 450) {
+          seenMessages.delete(entry);
+        } else {
+          break;
+        }
+      }
+    }
+    return false;
+  };
+
   const cfg = opts.config ?? loadConfig();
   const account = resolveTelegramAccount({
     cfg,
@@ -992,6 +1012,12 @@ export function createTelegramBot(opts: TelegramBotOptions) {
       const msg = ctx.message;
       if (!msg) return;
 
+      // Skip duplicate messages (can happen after restarts/reconnects)
+      if (isMessageSeen(msg.chat.id, msg.message_id)) {
+        logVerbose(`Skipping duplicate telegram message ${msg.chat.id}:${msg.message_id}`);
+        return;
+      }
+
       const chatId = msg.chat.id;
       const isGroup =
         msg.chat.type === "group" || msg.chat.type === "supergroup";
-- 
2.43.0

Testing

Tested locally - gateway restart no longer causes duplicate message processing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions