From 33ef0c68d36d1654447e93de537bec5b11bb76ae Mon Sep 17 00:00:00 2001 From: gavrielc Date: Sun, 1 Feb 2026 23:05:37 +0200 Subject: [PATCH] Fix message cursor to only advance on successful processing (#17) Previously, lastTimestamp was unconditionally advanced after each message, even if processMessage failed. This caused transient errors to permanently drop messages since they would never be retried. Now the cursor only advances after successful processing, implementing at-least-once delivery semantics. On failure, the loop breaks and the failed message will be retried on the next poll iteration. https://claude.ai/code/session_01SEQDWxXeZHe7t1bb5cw2CA Co-authored-by: Claude --- src/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5be2ebd..ffb01cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -522,12 +522,14 @@ async function startMessageLoop(): Promise { for (const msg of messages) { try { await processMessage(msg); + // Only advance timestamp after successful processing for at-least-once delivery + lastTimestamp = msg.timestamp; + saveState(); } catch (err) { - logger.error({ err, msg: msg.id }, 'Error processing message'); + logger.error({ err, msg: msg.id }, 'Error processing message, will retry'); + // Stop processing this batch - failed message will be retried next loop + break; } - // Advance timestamp after each message to avoid reprocessing on retry - lastTimestamp = msg.timestamp; - saveState(); } } catch (err) { logger.error({ err }, 'Error in message loop');