From 3b2ed8fe6fafa11f540c9f8b5c728eea01a7a63f Mon Sep 17 00:00:00 2001 From: YUJIE2002 Date: Sun, 1 Mar 2026 13:34:11 +0000 Subject: [PATCH] fix(telegram): prevent channel-level groups from leaking to all accounts in multi-account setups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In multi-account Telegram configurations, `mergeTelegramAccountConfig()` performs a shallow merge of channel-level config onto each account. This causes channel-level `groups` to be inherited by ALL accounts, including those whose bots are not members of the configured groups. When a secondary bot attempts to handle group messages for a group it is not in, the failure disrupts message delivery for all accounts — causing silent message loss with no errors in logs. Fix: exclude `groups` from the base spread (like `accounts` already is) and only apply channel-level groups as fallback in single-account setups for backward compatibility. Multi-account setups must use account-level groups config. Added 5 test cases covering single-account inheritance, multi-account isolation, account-level priority, and backward compatibility. Fixes #30673 --- src/telegram/accounts.test.ts | 101 ++++++++++++++++++++++++++++++++++ src/telegram/accounts.ts | 16 +++++- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/telegram/accounts.test.ts b/src/telegram/accounts.test.ts index 919ca989f..9d6440db2 100644 --- a/src/telegram/accounts.test.ts +++ b/src/telegram/accounts.test.ts @@ -168,3 +168,104 @@ describe("resolveTelegramAccount allowFrom precedence", () => { expect(resolved.config.groupAllowFrom).toBeUndefined(); }); }); + +describe("resolveTelegramAccount groups inheritance (#30673)", () => { + it("inherits channel-level groups in single-account setup", () => { + const resolved = resolveTelegramAccount({ + cfg: { + channels: { + telegram: { + groups: { "-100123": { requireMention: false } }, + accounts: { + default: { botToken: "123:default" }, + }, + }, + }, + }, + accountId: "default", + }); + + expect(resolved.config.groups).toEqual({ "-100123": { requireMention: false } }); + }); + + it("does NOT inherit channel-level groups to secondary account in multi-account setup", () => { + const resolved = resolveTelegramAccount({ + cfg: { + channels: { + telegram: { + groups: { "-100123": { requireMention: false } }, + accounts: { + default: { botToken: "123:default" }, + dev: { botToken: "456:dev" }, + }, + }, + }, + }, + accountId: "dev", + }); + + expect(resolved.config.groups).toBeUndefined(); + }); + + it("does NOT inherit channel-level groups to default account in multi-account setup", () => { + const resolved = resolveTelegramAccount({ + cfg: { + channels: { + telegram: { + groups: { "-100123": { requireMention: false } }, + accounts: { + default: { botToken: "123:default" }, + dev: { botToken: "456:dev" }, + }, + }, + }, + }, + accountId: "default", + }); + + expect(resolved.config.groups).toBeUndefined(); + }); + + it("uses account-level groups even in multi-account setup", () => { + const resolved = resolveTelegramAccount({ + cfg: { + channels: { + telegram: { + groups: { "-100999": { requireMention: true } }, + accounts: { + default: { + botToken: "123:default", + groups: { "-100123": { requireMention: false } }, + }, + dev: { botToken: "456:dev" }, + }, + }, + }, + }, + accountId: "default", + }); + + expect(resolved.config.groups).toEqual({ "-100123": { requireMention: false } }); + }); + + it("account-level groups takes priority over channel-level in single-account setup", () => { + const resolved = resolveTelegramAccount({ + cfg: { + channels: { + telegram: { + groups: { "-100999": { requireMention: true } }, + accounts: { + default: { + botToken: "123:default", + groups: { "-100123": { requireMention: false } }, + }, + }, + }, + }, + }, + accountId: "default", + }); + + expect(resolved.config.groups).toEqual({ "-100123": { requireMention: false } }); + }); +}); diff --git a/src/telegram/accounts.ts b/src/telegram/accounts.ts index 9df297180..5350baae8 100644 --- a/src/telegram/accounts.ts +++ b/src/telegram/accounts.ts @@ -84,10 +84,22 @@ function resolveAccountConfig( } function mergeTelegramAccountConfig(cfg: OpenClawConfig, accountId: string): TelegramAccountConfig { - const { accounts: _ignored, ...base } = (cfg.channels?.telegram ?? + const { accounts: _ignored, groups: channelGroups, ...base } = (cfg.channels?.telegram ?? {}) as TelegramAccountConfig & { accounts?: unknown }; const account = resolveAccountConfig(cfg, accountId) ?? {}; - return { ...base, ...account }; + + // In multi-account setups, channel-level `groups` must NOT be inherited by + // accounts that don't have their own `groups` config. A bot that is not a + // member of a configured group will fail when handling group messages, and + // this failure disrupts message delivery for *all* accounts. + // Single-account setups keep backward compat: channel-level groups still + // applies when the account has no override. + // See: https://github.com/openclaw/openclaw/issues/30673 + const configuredAccountIds = Object.keys(cfg.channels?.telegram?.accounts ?? {}); + const isMultiAccount = configuredAccountIds.length > 1; + const groups = account.groups ?? (isMultiAccount ? undefined : channelGroups); + + return { ...base, ...account, groups }; } export function createTelegramActionGate(params: {