From 0ac484974cd59fb93bd74f20676c0c44831a50cb Mon Sep 17 00:00:00 2001 From: Jan Renz Date: Sun, 17 May 2026 23:35:22 +0200 Subject: [PATCH] fix(pulse/imessage): block startIMessage until stop, ending supervisor restart loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit startIMessage sets up the setInterval poll timer and then returns immediately. The supervisor in pulse.ts treats that return as a clean exit and re-invokes the module every 10 seconds, where the running-flag guard catches the second call and logs 'iMessage module already running, ignoring start request'. The poll loop itself works correctly throughout — this is pure log spam, plus an unnecessary every-10s call overhead — but it's the same shape of bug the telegram module solved by awaiting bot.start() (see modules/telegram.ts:344-348 comment). Fix mirrors that pattern: at end of startIMessage, await a Promise whose resolve() is captured in a module-scoped stopResolve. stopIMessage releases it after clearing the timer and persisting the cursor. With this, supervisor sees startIMessage as long-running and stops the 10s restart cycle. Reproduces on any healthy install with Full Disk Access granted and an allowed_handles list. Observable in pulse-stdout.log as alternating 'imessage exited cleanly, restarting in 10s' / 'iMessage module already running, ignoring start request' lines every 10s. --- .../v5.0.0/.claude/PAI/PULSE/modules/imessage.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Releases/v5.0.0/.claude/PAI/PULSE/modules/imessage.ts b/Releases/v5.0.0/.claude/PAI/PULSE/modules/imessage.ts index c6fe58bce2..95e7a3182a 100644 --- a/Releases/v5.0.0/.claude/PAI/PULSE/modules/imessage.ts +++ b/Releases/v5.0.0/.claude/PAI/PULSE/modules/imessage.ts @@ -65,6 +65,7 @@ const LOGS_DIR = join(HOME, ".claude", "PAI", "Pulse", "logs", "imessage") let pollTimer: ReturnType | null = null let running = false +let stopResolve: (() => void) | null = null let startedAt = 0 let messagesReceived = 0 let messagesResponded = 0 @@ -399,6 +400,13 @@ export async function startIMessage(config: IMessageConfig): Promise { pollTimer = setInterval(poll, pollIntervalMs) log("info", `iMessage module polling every ${pollIntervalMs}ms`) + + // Block until stopIMessage() resolves this. Without this await the function + // returns immediately after setInterval and the supervisor restarts it every + // 10s — see modules/telegram.ts for the same pattern documented. + await new Promise((resolve) => { + stopResolve = resolve + }) } /** @@ -430,6 +438,13 @@ export async function stopIMessage(): Promise { messagesReceived, messagesResponded, }) + + // Release startIMessage() so it can return cleanly. + if (stopResolve) { + const resolve = stopResolve + stopResolve = null + resolve() + } } /**