From 3a4d340f8045dd5574b8d6025ee370a7b81c50c9 Mon Sep 17 00:00:00 2001 From: Gavriel Cohen Date: Thu, 5 Feb 2026 00:18:24 +0200 Subject: [PATCH] Fix duplicate responses caused by reconnect-stacking loops WhatsApp reconnections called startMessageLoop/startSchedulerLoop/ startIpcWatcher and setInterval again without stopping the previous instances, creating parallel loops that processed the same messages. Add guard flags so each loop starts only once per process lifetime. Co-Authored-By: Claude Opus 4.5 --- src/index.ts | 30 ++++++++++++++++++++++++------ src/task-scheduler.ts | 7 +++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 20a6914..7a1f516 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,10 @@ let registeredGroups: Record = {}; let lastAgentTimestamp: Record = {}; // LID to phone number mapping (WhatsApp now sends LID JIDs for self-chats) let lidToPhoneMap: Record = {}; +// Guards to prevent duplicate loops on WhatsApp reconnect +let messageLoopRunning = false; +let ipcWatcherRunning = false; +let groupSyncTimerStarted = false; /** * Translate a JID from LID format to phone format if we have a mapping. @@ -292,6 +296,12 @@ async function sendMessage(jid: string, text: string): Promise { } function startIpcWatcher(): void { + if (ipcWatcherRunning) { + logger.debug('IPC watcher already running, skipping duplicate start'); + return; + } + ipcWatcherRunning = true; + const ipcBaseDir = path.join(DATA_DIR, 'ipc'); fs.mkdirSync(ipcBaseDir, { recursive: true }); @@ -697,12 +707,15 @@ async function connectWhatsApp(): Promise { syncGroupMetadata().catch((err) => logger.error({ err }, 'Initial group sync failed'), ); - // Set up daily sync timer - setInterval(() => { - syncGroupMetadata().catch((err) => - logger.error({ err }, 'Periodic group sync failed'), - ); - }, GROUP_SYNC_INTERVAL_MS); + // Set up daily sync timer (only once) + if (!groupSyncTimerStarted) { + groupSyncTimerStarted = true; + setInterval(() => { + syncGroupMetadata().catch((err) => + logger.error({ err }, 'Periodic group sync failed'), + ); + }, GROUP_SYNC_INTERVAL_MS); + } startSchedulerLoop({ sendMessage, registeredGroups: () => registeredGroups, @@ -745,6 +758,11 @@ async function connectWhatsApp(): Promise { } async function startMessageLoop(): Promise { + if (messageLoopRunning) { + logger.debug('Message loop already running, skipping duplicate start'); + return; + } + messageLoopRunning = true; logger.info(`NanoClaw running (trigger: @${ASSISTANT_NAME})`); while (true) { diff --git a/src/task-scheduler.ts b/src/task-scheduler.ts index 430a290..bb3857b 100644 --- a/src/task-scheduler.ts +++ b/src/task-scheduler.ts @@ -141,7 +141,14 @@ async function runTask( updateTaskAfterRun(task.id, nextRun, resultSummary); } +let schedulerRunning = false; + export function startSchedulerLoop(deps: SchedulerDependencies): void { + if (schedulerRunning) { + logger.debug('Scheduler loop already running, skipping duplicate start'); + return; + } + schedulerRunning = true; logger.info('Scheduler loop started'); const loop = async () => {