From a02c40483ed883d982f4d994923695c01f21d1c7 Mon Sep 17 00:00:00 2001 From: SidQin-cyber Date: Thu, 26 Feb 2026 20:22:10 +0800 Subject: [PATCH] fix(telegram): degrade command sync on BOT_COMMANDS_TOO_MUCH When Telegram rejects native command registration for excessive commands, progressively retry with fewer commands instead of hard-failing startup. Made-with: Cursor --- src/telegram/bot-native-command-menu.test.ts | 38 +++++++++++++ src/telegram/bot-native-command-menu.ts | 59 ++++++++++++++++++-- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/telegram/bot-native-command-menu.test.ts b/src/telegram/bot-native-command-menu.test.ts index cabea3132d5..b73d4735875 100644 --- a/src/telegram/bot-native-command-menu.test.ts +++ b/src/telegram/bot-native-command-menu.test.ts @@ -86,4 +86,42 @@ describe("bot-native-command-menu", () => { expect(callOrder).toEqual(["delete", "set"]); }); + + it("retries with fewer commands on BOT_COMMANDS_TOO_MUCH", async () => { + const deleteMyCommands = vi.fn(async () => undefined); + const setMyCommands = vi + .fn() + .mockRejectedValueOnce(new Error("400: Bad Request: BOT_COMMANDS_TOO_MUCH")) + .mockResolvedValue(undefined); + const runtimeLog = vi.fn(); + + syncTelegramMenuCommands({ + bot: { + api: { + deleteMyCommands, + setMyCommands, + }, + } as unknown as Parameters[0]["bot"], + runtime: { + log: runtimeLog, + error: vi.fn(), + exit: vi.fn(), + } as Parameters[0]["runtime"], + commandsToRegister: Array.from({ length: 100 }, (_, i) => ({ + command: `cmd_${i}`, + description: `Command ${i}`, + })), + }); + + await vi.waitFor(() => { + expect(setMyCommands).toHaveBeenCalledTimes(2); + }); + const firstPayload = setMyCommands.mock.calls[0]?.[0] as Array; + const secondPayload = setMyCommands.mock.calls[1]?.[0] as Array; + expect(firstPayload).toHaveLength(100); + expect(secondPayload).toHaveLength(80); + expect(runtimeLog).toHaveBeenCalledWith( + "Telegram rejected 100 commands (BOT_COMMANDS_TOO_MUCH); retrying with 80.", + ); + }); }); diff --git a/src/telegram/bot-native-command-menu.ts b/src/telegram/bot-native-command-menu.ts index 5528fd06ff7..0f993b7cdba 100644 --- a/src/telegram/bot-native-command-menu.ts +++ b/src/telegram/bot-native-command-menu.ts @@ -7,6 +7,7 @@ import type { RuntimeEnv } from "../runtime.js"; import { withTelegramApiErrorLogging } from "./api-logging.js"; export const TELEGRAM_MAX_COMMANDS = 100; +const TELEGRAM_COMMAND_RETRY_RATIO = 0.8; export type TelegramMenuCommand = { command: string; @@ -18,6 +19,31 @@ type TelegramPluginCommandSpec = { description: string; }; +function isBotCommandsTooMuchError(err: unknown): boolean { + if (!err) { + return false; + } + const pattern = /\bBOT_COMMANDS_TOO_MUCH\b/i; + if (typeof err === "string") { + return pattern.test(err); + } + if (err instanceof Error) { + if (pattern.test(err.message)) { + return true; + } + } + if (typeof err === "object") { + const maybe = err as { description?: unknown; message?: unknown }; + if (typeof maybe.description === "string" && pattern.test(maybe.description)) { + return true; + } + if (typeof maybe.message === "string" && pattern.test(maybe.message)) { + return true; + } + } + return false; +} + export function buildPluginTelegramMenuCommands(params: { specs: TelegramPluginCommandSpec[]; existingCommands: Set; @@ -93,11 +119,34 @@ export function syncTelegramMenuCommands(params: { return; } - await withTelegramApiErrorLogging({ - operation: "setMyCommands", - runtime, - fn: () => bot.api.setMyCommands(commandsToRegister), - }); + let retryCommands = commandsToRegister; + while (retryCommands.length > 0) { + try { + await withTelegramApiErrorLogging({ + operation: "setMyCommands", + runtime, + fn: () => bot.api.setMyCommands(retryCommands), + }); + return; + } catch (err) { + if (!isBotCommandsTooMuchError(err)) { + throw err; + } + const nextCount = Math.floor(retryCommands.length * TELEGRAM_COMMAND_RETRY_RATIO); + const reducedCount = + nextCount < retryCommands.length ? nextCount : retryCommands.length - 1; + if (reducedCount <= 0) { + runtime.error?.( + "Telegram rejected native command registration (BOT_COMMANDS_TOO_MUCH); leaving menu empty. Reduce commands or disable channels.telegram.commands.native.", + ); + return; + } + runtime.log?.( + `Telegram rejected ${retryCommands.length} commands (BOT_COMMANDS_TOO_MUCH); retrying with ${reducedCount}.`, + ); + retryCommands = retryCommands.slice(0, reducedCount); + } + } }; void sync().catch((err) => {