mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(whatsapp): stop retry loop on non-retryable 440 close
This commit is contained in:
committed by
Peter Steinberger
parent
def993dbd8
commit
e22a2d77ba
@@ -145,6 +145,56 @@ describe("web auto-reply", () => {
|
||||
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining(scenario.expectedError));
|
||||
}
|
||||
});
|
||||
|
||||
it("treats status 440 as non-retryable and stops without retrying", async () => {
|
||||
const closeResolvers: Array<(reason?: unknown) => void> = [];
|
||||
const sleep = vi.fn(async () => {});
|
||||
const listenerFactory = vi.fn(async () => {
|
||||
const onClose = new Promise<unknown>((res) => {
|
||||
closeResolvers.push(res);
|
||||
});
|
||||
return { close: vi.fn(), onClose };
|
||||
});
|
||||
const { runtime, controller, run } = startMonitorWebChannel({
|
||||
monitorWebChannelFn: monitorWebChannel as never,
|
||||
listenerFactory,
|
||||
sleep,
|
||||
reconnect: { initialMs: 10, maxMs: 10, maxAttempts: 3, factor: 1.1 },
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
expect(listenerFactory).toHaveBeenCalledTimes(1);
|
||||
closeResolvers.shift()?.({
|
||||
status: 440,
|
||||
isLoggedOut: false,
|
||||
error: "Unknown Stream Errored (conflict)",
|
||||
});
|
||||
|
||||
const completedQuickly = await Promise.race([
|
||||
run.then(() => true),
|
||||
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 60)),
|
||||
]);
|
||||
|
||||
if (!completedQuickly) {
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expect(listenerFactory).toHaveBeenCalledTimes(2);
|
||||
},
|
||||
{ timeout: 250, interval: 2 },
|
||||
);
|
||||
controller.abort();
|
||||
closeResolvers[1]?.({ status: 499, isLoggedOut: false, error: "aborted" });
|
||||
await run;
|
||||
}
|
||||
|
||||
expect(completedQuickly).toBe(true);
|
||||
expect(listenerFactory).toHaveBeenCalledTimes(1);
|
||||
expect(sleep).not.toHaveBeenCalled();
|
||||
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("status 440"));
|
||||
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("session conflict"));
|
||||
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("Stopping web monitoring"));
|
||||
});
|
||||
|
||||
it("forces reconnect when watchdog closes without onClose", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -31,6 +31,12 @@ import { createWebOnMessageHandler } from "./monitor/on-message.js";
|
||||
import type { WebChannelStatus, WebInboundMsg, WebMonitorTuning } from "./types.js";
|
||||
import { isLikelyWhatsAppCryptoError } from "./util.js";
|
||||
|
||||
function isNonRetryableWebCloseStatus(statusCode: unknown): boolean {
|
||||
// WhatsApp 440 = session conflict ("Unknown Stream Errored (conflict)").
|
||||
// This is persistent until the operator resolves the conflicting session.
|
||||
return statusCode === 440;
|
||||
}
|
||||
|
||||
export async function monitorWebChannel(
|
||||
verbose: boolean,
|
||||
listenerFactory: typeof monitorWebInbox | undefined = monitorWebInbox,
|
||||
@@ -402,6 +408,22 @@ export async function monitorWebChannel(
|
||||
break;
|
||||
}
|
||||
|
||||
if (isNonRetryableWebCloseStatus(statusCode)) {
|
||||
reconnectLogger.warn(
|
||||
{
|
||||
connectionId,
|
||||
status: statusCode,
|
||||
error: errorStr,
|
||||
},
|
||||
"web reconnect: non-retryable close status; stopping monitor",
|
||||
);
|
||||
runtime.error(
|
||||
`WhatsApp Web connection closed (status ${statusCode}: session conflict). Resolve conflicting WhatsApp Web sessions, then relink with \`${formatCliCommand("openclaw channels login --channel web")}\`. Stopping web monitoring.`,
|
||||
);
|
||||
await closeListener();
|
||||
break;
|
||||
}
|
||||
|
||||
reconnectAttempts += 1;
|
||||
status.reconnectAttempts = reconnectAttempts;
|
||||
emitStatus();
|
||||
|
||||
Reference in New Issue
Block a user