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 <noreply@anthropic.com>
This commit is contained in:
Gavriel Cohen
2026-02-05 00:18:24 +02:00
parent 1f8cd26a83
commit 3a4d340f80
2 changed files with 31 additions and 6 deletions

View File

@@ -52,6 +52,10 @@ let registeredGroups: Record<string, RegisteredGroup> = {};
let lastAgentTimestamp: Record<string, string> = {};
// LID to phone number mapping (WhatsApp now sends LID JIDs for self-chats)
let lidToPhoneMap: Record<string, string> = {};
// 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<void> {
}
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<void> {
syncGroupMetadata().catch((err) =>
logger.error({ err }, 'Initial group sync failed'),
);
// Set up daily sync timer
// 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<void> {
}
async function startMessageLoop(): Promise<void> {
if (messageLoopRunning) {
logger.debug('Message loop already running, skipping duplicate start');
return;
}
messageLoopRunning = true;
logger.info(`NanoClaw running (trigger: @${ASSISTANT_NAME})`);
while (true) {

View File

@@ -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 () => {