Skip to content

Commit 87a9e27

Browse files
retry whatsapp session init conflicts (#101106)
1 parent f310605 commit 87a9e27

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

extensions/whatsapp/src/inbound/monitor.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
formatLocationText,
1515
} from "openclaw/plugin-sdk/channel-inbound";
1616
import { createInboundDebouncer } from "openclaw/plugin-sdk/channel-inbound-debounce";
17+
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
1718
import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
1819
import {
1920
asDateTimestampMs,
@@ -104,12 +105,31 @@ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress
104105
const GROUP_META_TTL_MS = 5 * 60 * 1000;
105106
const BAILEYS_MESSAGE_TTL_MS = 10 * 60 * 1000;
106107
const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5_000;
108+
const REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE = /reply session initialization conflicted for \S+/u;
107109
export const WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES = 500;
108110

109111
type WhatsAppGroupMetadataCacheEntry = {
110112
subject?: string;
111113
expires: number;
112114
};
115+
116+
function resolveRetryableWhatsAppInboundError(
117+
error: unknown,
118+
): WhatsAppRetryableInboundError | null {
119+
if (error instanceof WhatsAppRetryableInboundError) {
120+
return error;
121+
}
122+
const hasSessionInitConflict = collectErrorGraphCandidates(error, (current) => [
123+
current.cause,
124+
current.error,
125+
]).some((candidate) =>
126+
REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(formatErrorMessage(candidate)),
127+
);
128+
if (!hasSessionInitConflict) {
129+
return null;
130+
}
131+
return new WhatsAppRetryableInboundError(formatErrorMessage(error), { cause: error });
132+
}
113133
export type WhatsAppGroupMetadataCache = Map<string, WhatsAppGroupMetadataCacheEntry>;
114134
export type WhatsAppBaileysCacheEntry<T> = {
115135
expiresAt: number;
@@ -465,12 +485,13 @@ export async function attachWebInboxToSocket(
465485
(entry): entry is QueuedInboundMessage & { readReceipt: WhatsAppReadReceiptTarget } =>
466486
Boolean(entry.readReceipt),
467487
);
468-
if (error instanceof WhatsAppRetryableInboundError) {
469-
dedupeKeys.forEach((dedupeKey) => releaseRecentInboundMessage(dedupeKey, error));
488+
const retryableError = resolveRetryableWhatsAppInboundError(error);
489+
if (retryableError) {
490+
dedupeKeys.forEach((dedupeKey) => releaseRecentInboundMessage(dedupeKey, retryableError));
470491
await Promise.all(
471492
durableEntries.map((entry) =>
472493
durableInboundJournal.release(entry.durableId, {
473-
lastError: formatError(error),
494+
lastError: formatError(retryableError),
474495
}),
475496
),
476497
);

extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test-support.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,39 @@ describe("web monitor inbox", () => {
15961596
await listener.close();
15971597
});
15981598

1599+
it("retries redelivered messages after reply session initialization conflicts", async () => {
1600+
let attempts = 0;
1601+
const onMessage = vi.fn(async () => {
1602+
attempts += 1;
1603+
if (attempts === 1) {
1604+
throw new Error(
1605+
"reply session initialization conflicted for agent:main:whatsapp:direct:+15551234567",
1606+
);
1607+
}
1608+
});
1609+
1610+
const { listener, sock } = await startInboxMonitor(onMessage as InboxOnMessage);
1611+
const upsert = buildNotifyMessageUpsert({
1612+
id: nextMessageId("session-init-conflict"),
1613+
remoteJid: "[email protected]",
1614+
text: "ping",
1615+
timestamp: 1_700_000_000,
1616+
pushName: "Tester",
1617+
});
1618+
1619+
sock.ev.emit("messages.upsert", upsert);
1620+
await waitForMessageCalls(onMessage, 1);
1621+
expect(sock.readMessages).not.toHaveBeenCalled();
1622+
1623+
sock.ev.emit("messages.upsert", upsert);
1624+
await waitForMessageCalls(onMessage, 2);
1625+
await vi.waitFor(() => {
1626+
expect(sock.readMessages).toHaveBeenCalledTimes(1);
1627+
});
1628+
1629+
await listener.close();
1630+
});
1631+
15991632
it("resolves LID JIDs using Baileys LID mapping store", async () => {
16001633
const onMessage = vi.fn(async () => {});
16011634

0 commit comments

Comments
 (0)