diff --git a/extensions/telegram/src/bot-handlers.runtime.ts b/extensions/telegram/src/bot-handlers.runtime.ts index 5d7bdd9d41b..015642f986c 100644 --- a/extensions/telegram/src/bot-handlers.runtime.ts +++ b/extensions/telegram/src/bot-handlers.runtime.ts @@ -674,6 +674,12 @@ export const registerTelegramHandlers = ({ } } + const TELEGRAM_PERMANENT_CALLBACK_EDIT_ERROR_RE = + /400:\s*Bad Request:\s*message to edit not found|400:\s*Bad Request:\s*there is no text in the message to edit|MESSAGE_ID_INVALID|message can't be edited/i; + + const isPermanentTelegramCallbackEditError = (err: unknown): boolean => + TELEGRAM_PERMANENT_CALLBACK_EDIT_ERROR_RE.test(String(err)); + const resolveTelegramEventAuthorizationContext = async (params: { chatId: number; isGroup: boolean; @@ -1672,10 +1678,15 @@ export const registerTelegramHandlers = ({ messageIdOverride: callback.id, }); } catch (err) { - runtime.error?.(danger(`callback handler failed: ${String(err)}`)); if (err instanceof TelegramRetryableCallbackError) { + if (isPermanentTelegramCallbackEditError(err.cause)) { + logVerbose(`telegram: swallowing permanent callback edit error: ${String(err.cause)}`); + return; + } + runtime.error?.(danger(`callback handler failed: ${String(err)}`)); throw err.cause; } + runtime.error?.(danger(`callback handler failed: ${String(err)}`)); } }); diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index eef3764e1f5..6d3674aa5d7 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -3328,6 +3328,62 @@ describe("createTelegramBot", () => { expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Commands (2/"); }); + it("treats permanent command pagination edit failures as completed updates", async () => { + sequentializeSpy.mockImplementationOnce( + () => async (_ctx: unknown, next: () => Promise) => { + await next(); + }, + ); + + const onUpdateId = vi.fn(); + createTelegramBot({ + token: "tok", + updateOffset: { + lastUpdateId: 776, + onUpdateId, + }, + }); + + const callbackHandler = getOnHandler("callback_query"); + const ctx = { + update: { update_id: 777 }, + callbackQuery: { + id: "cbq-commands-permanent-edit-1", + data: "commands_page_2:main", + from: { id: 9, first_name: "Ada", username: "ada_bot" }, + message: { + chat: { id: 1234, type: "private" }, + date: 1736380800, + message_id: 20, + }, + }, + me: { username: "openclaw_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }; + + editMessageTextSpy.mockRejectedValueOnce( + new Error("400: Bad Request: message can't be edited"), + ); + + await expect( + runTelegramMiddlewareChain({ + ctx, + finalHandler: callbackHandler, + }), + ).resolves.toBeUndefined(); + + await vi.waitFor(() => { + expect(onUpdateId).toHaveBeenCalledWith(777); + }); + + await runTelegramMiddlewareChain({ + ctx, + finalHandler: callbackHandler, + }); + + expect(editMessageTextSpy).toHaveBeenCalledTimes(1); + }); + it("retries command pagination callbacks after a bubbled preflight failure", async () => { const listSkillCommandsMock = listSkillCommandsForAgents as unknown as ReturnType; listSkillCommandsMock.mockClear();