Skip to content

Commit 8035e1d

Browse files
gavrielcnaveensparkpottertechclaude
authored
fix: add .catch() handlers to fire-and-forget async calls (nanocoai#221) (nanocoai#355)
Several async calls in the message loop and group queue are fire-and-forget without .catch() handlers. When WhatsApp disconnects or containers fail unexpectedly, these produce unhandled rejections that can crash the process. Add explicit .catch() at each call site so errors are logged with full context (groupJid, taskId) instead of crashing: - channel.setTyping() in message loop (adapted for channel abstraction) - startMessageLoop() in main() - runForGroup() and runTask() in group-queue (5 call sites) Closes nanocoai#221 Co-authored-by: Naveen Jain <1779929+naveenspark@users.noreply.github.com> Co-authored-by: Skip Potter <skip.potter.va@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6a5f272 commit 8035e1d

2 files changed

Lines changed: 25 additions & 8 deletions

File tree

src/group-queue.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export class GroupQueue {
8080
return;
8181
}
8282

83-
this.runForGroup(groupJid, 'messages');
83+
this.runForGroup(groupJid, 'messages').catch((err) =>
84+
logger.error({ groupJid, err }, 'Unhandled error in runForGroup'),
85+
);
8486
}
8587

8688
enqueueTask(groupJid: string, taskId: string, fn: () => Promise<void>): void {
@@ -116,7 +118,9 @@ export class GroupQueue {
116118
}
117119

118120
// Run immediately
119-
this.runTask(groupJid, { id: taskId, groupJid, fn });
121+
this.runTask(groupJid, { id: taskId, groupJid, fn }).catch((err) =>
122+
logger.error({ groupJid, taskId, err }, 'Unhandled error in runTask'),
123+
);
120124
}
121125

122126
registerProcess(groupJid: string, proc: ChildProcess, containerName: string, groupFolder?: string): void {
@@ -273,13 +277,17 @@ export class GroupQueue {
273277
// Tasks first (they won't be re-discovered from SQLite like messages)
274278
if (state.pendingTasks.length > 0) {
275279
const task = state.pendingTasks.shift()!;
276-
this.runTask(groupJid, task);
280+
this.runTask(groupJid, task).catch((err) =>
281+
logger.error({ groupJid, taskId: task.id, err }, 'Unhandled error in runTask (drain)'),
282+
);
277283
return;
278284
}
279285

280286
// Then pending messages
281287
if (state.pendingMessages) {
282-
this.runForGroup(groupJid, 'drain');
288+
this.runForGroup(groupJid, 'drain').catch((err) =>
289+
logger.error({ groupJid, err }, 'Unhandled error in runForGroup (drain)'),
290+
);
283291
return;
284292
}
285293

@@ -298,9 +306,13 @@ export class GroupQueue {
298306
// Prioritize tasks over messages
299307
if (state.pendingTasks.length > 0) {
300308
const task = state.pendingTasks.shift()!;
301-
this.runTask(nextJid, task);
309+
this.runTask(nextJid, task).catch((err) =>
310+
logger.error({ groupJid: nextJid, taskId: task.id, err }, 'Unhandled error in runTask (waiting)'),
311+
);
302312
} else if (state.pendingMessages) {
303-
this.runForGroup(nextJid, 'drain');
313+
this.runForGroup(nextJid, 'drain').catch((err) =>
314+
logger.error({ groupJid: nextJid, err }, 'Unhandled error in runForGroup (waiting)'),
315+
);
304316
}
305317
// If neither pending, skip this group
306318
}

src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ async function startMessageLoop(): Promise<void> {
370370
messagesToSend[messagesToSend.length - 1].timestamp;
371371
saveState();
372372
// Show typing indicator while the container processes the piped message
373-
channel.setTyping?.(chatJid, true);
373+
channel.setTyping?.(chatJid, true)?.catch((err) =>
374+
logger.warn({ chatJid, err }, 'Failed to set typing indicator'),
375+
);
374376
} else {
375377
// No active container — enqueue for a new one
376378
queue.enqueueMessageCheck(chatJid);
@@ -466,7 +468,10 @@ async function main(): Promise<void> {
466468
});
467469
queue.setProcessMessagesFn(processGroupMessages);
468470
recoverPendingMessages();
469-
startMessageLoop();
471+
startMessageLoop().catch((err) => {
472+
logger.fatal({ err }, 'Message loop crashed unexpectedly');
473+
process.exit(1);
474+
});
470475
}
471476

472477
// Guard: only run when executed directly, not when imported by tests

0 commit comments

Comments
 (0)