From a18743eabc03c69ed89a0421fa9601962b415141 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 9 Jan 2026 22:40:41 +0000 Subject: [PATCH] fix(web): improve WhatsApp Web listener errors (#612, thanks @YuriNachos) --- CHANGELOG.md | 1 + src/web/outbound.test.ts | 13 +++++++++++++ src/web/outbound.ts | 30 ++++++++++++------------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb273a1bd..55944194e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Hooks: default hook agent delivery to true. (#533) — thanks @mcinteerj - Hooks: normalize hook agent providers (aliases + msteams support). - WhatsApp: route queued replies to the original sender instead of the bot's own number. (#534) — thanks @mcinteerj +- WhatsApp: improve "no active web listener" errors (include account + relink hint). (#612) — thanks @YuriNachos - WhatsApp: add broadcast groups for multi-agent replies. (#547) — thanks @pasogott - WhatsApp: resolve @lid inbound senders via auth-dir mapping fallback + shared resolver. (#365) - iMessage: isolate group-ish threads by chat_id. (#535) — thanks @mdahmann diff --git a/src/web/outbound.test.ts b/src/web/outbound.test.ts index f92c7fea7..26bd70b0f 100644 --- a/src/web/outbound.test.ts +++ b/src/web/outbound.test.ts @@ -51,6 +51,19 @@ describe("web outbound", () => { ); }); + it("throws a helpful error when no active listener exists", async () => { + setActiveWebListener(null); + await expect( + sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), + ).rejects.toThrow(/No active WhatsApp Web listener/); + await expect( + sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), + ).rejects.toThrow(/providers login/); + await expect( + sendMessageWhatsApp("+1555", "hi", { verbose: false, accountId: "work" }), + ).rejects.toThrow(/account: work/); + }); + it("maps audio to PTT with opus mime when ogg", async () => { const buf = Buffer.from("audio"); loadWebMediaMock.mockResolvedValueOnce({ diff --git a/src/web/outbound.ts b/src/web/outbound.ts index e5e400036..5ae333431 100644 --- a/src/web/outbound.ts +++ b/src/web/outbound.ts @@ -13,6 +13,15 @@ const outboundLog = createSubsystemLogger("gateway/providers/whatsapp").child( "outbound", ); +function requireActiveListener(accountId?: string | null) { + const active = getActiveWebListener(accountId); + if (active) return active; + const id = (accountId ?? "").trim() || "default"; + throw new Error( + `No active WhatsApp Web listener (account: ${id}). Start the gateway, then link WhatsApp with: clawdbot providers login --provider whatsapp --account ${id}.`, + ); +} + export async function sendMessageWhatsApp( to: string, body: string, @@ -26,12 +35,7 @@ export async function sendMessageWhatsApp( let text = body; const correlationId = randomUUID(); const startedAt = Date.now(); - const active = getActiveWebListener(options.accountId); - if (!active) { - throw new Error( - "No active gateway listener. Start the gateway before sending messages.", - ); - } + const active = requireActiveListener(options.accountId); const logger = getChildLogger({ module: "web-outbound", correlationId, @@ -108,12 +112,7 @@ export async function sendReactionWhatsApp( }, ): Promise { const correlationId = randomUUID(); - const active = getActiveWebListener(options.accountId); - if (!active) { - throw new Error( - "No active gateway listener. Start the gateway before sending reactions.", - ); - } + const active = requireActiveListener(options.accountId); const logger = getChildLogger({ module: "web-outbound", correlationId, @@ -149,12 +148,7 @@ export async function sendPollWhatsApp( ): Promise<{ messageId: string; toJid: string }> { const correlationId = randomUUID(); const startedAt = Date.now(); - const active = getActiveWebListener(options.accountId); - if (!active) { - throw new Error( - "No active gateway listener. Start the gateway before sending polls.", - ); - } + const active = requireActiveListener(options.accountId); const logger = getChildLogger({ module: "web-outbound", correlationId,