diff --git a/CHANGELOG.md b/CHANGELOG.md index 0925a614b1b..799951ab06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai - Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions. - Telegram/Polling: clear Telegram webhooks (`deleteWebhook`) before starting long-poll `getUpdates`, including retry handling for transient cleanup failures. - Telegram/Webhook: add `channels.telegram.webhookPort` config support and pass it through plugin startup wiring to the monitor listener. +- Telegram/Media: send a user-facing Telegram reply when media download fails (non-size errors) instead of silently dropping the message. - Signal/RPC: guard malformed Signal RPC JSON responses with a clear status-scoped error and add regression coverage for invalid JSON responses. (#22995) Thanks @adhitShet. - Gateway/Subagents: guard gateway and subagent session-key/message trim paths against undefined inputs to prevent early `Cannot read properties of undefined (reading 'trim')` crashes during subagent spawn and wait flows. - Agents/Workspace: guard `resolveUserPath` against undefined/null input to prevent `Cannot read properties of undefined (reading 'trim')` crashes when workspace paths are missing in embedded runner flows. diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 33f94331ee5..5d3cfc30b4a 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -722,7 +722,16 @@ export const registerTelegramHandlers = ({ logger.warn({ chatId, error: String(mediaErr) }, oversizeLogMessage); return; } - throw mediaErr; + logger.warn({ chatId, error: String(mediaErr) }, "media fetch failed"); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + fn: () => + bot.api.sendMessage(chatId, "⚠️ Failed to download media. Please try again.", { + reply_to_message_id: msg.message_id, + }), + }).catch(() => {}); + return; } // Skip sticker-only messages where the sticker was skipped (animated/video) diff --git a/src/telegram/bot.create-telegram-bot.test.ts b/src/telegram/bot.create-telegram-bot.test.ts index a76a8bb0b16..fdd2eb32ecc 100644 --- a/src/telegram/bot.create-telegram-bot.test.ts +++ b/src/telegram/bot.create-telegram-bot.test.ts @@ -1883,6 +1883,46 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); fetchSpy.mockRestore(); }); + it("notifies users when media download fails for direct messages", async () => { + loadConfig.mockReturnValue({ + channels: { + telegram: { dmPolicy: "open", allowFrom: ["*"] }, + }, + }); + sendMessageSpy.mockClear(); + replySpy.mockClear(); + const fetchSpy = vi + .spyOn(globalThis, "fetch") + .mockImplementation(async () => + Promise.reject(new Error("MediaFetchError: Failed to fetch media")), + ); + + try { + createTelegramBot({ token: "tok" }); + const handler = getOnHandler("message") as (ctx: Record) => Promise; + + await handler({ + message: { + chat: { id: 1234, type: "private" }, + message_id: 411, + date: 1736380800, + photo: [{ file_id: "p1" }], + from: { id: 55, is_bot: false, first_name: "u" }, + }, + me: { username: "openclaw_bot" }, + getFile: async () => ({ file_path: "photos/p1.jpg" }), + }); + + expect(sendMessageSpy).toHaveBeenCalledWith( + 1234, + "⚠️ Failed to download media. Please try again.", + { reply_to_message_id: 411 }, + ); + expect(replySpy).not.toHaveBeenCalled(); + } finally { + fetchSpy.mockRestore(); + } + }); it("processes remaining media group photos when one photo download fails", async () => { onSpy.mockReset(); replySpy.mockReset();