From 978211dc65e38c54206273c32a64d39f82432e6b Mon Sep 17 00:00:00 2001 From: Paul Kloppers Date: Thu, 14 May 2026 12:42:55 +0200 Subject: [PATCH] fix(redstone): close race in withSlowToolIndicator orphaning wiki loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The setTimeout callback awaited sendMessage before installing the rotation interval. If the tool resolved during that await, the finally cleanup ran before rotateTimer existed, leaving an interval that edited the "reading the wiki…" placeholder forever. Added a `done` flag so the callback bails (and deletes the placeholder) if the tool already finished. --- bot/bot.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot/bot.ts b/bot/bot.ts index 7916162..ed82ce1 100644 --- a/bot/bot.ts +++ b/bot/bot.ts @@ -1524,10 +1524,19 @@ async function withSlowToolIndicator(chatId: number, toolName: string, fn: () if (!WIKI_TOOL_RE.test(toolName)) return fn(); let placeholderId: number | null = null; let rotateTimer: ReturnType | null = null; + let done = false; let phraseIdx = 0; const showTimer = setTimeout(async () => { try { const m = await bot.api.sendMessage(chatId, SLOW_TOOL_PHRASES[0]); + // fn() may have resolved while sendMessage was in flight — if so the + // finally block already ran and saw no placeholder/timer to clean up. + // Delete the just-sent message and bail instead of installing an + // orphaned rotation loop that nothing will ever clear. + if (done) { + try { await bot.api.deleteMessage(chatId, m.message_id); } catch { /* ignore */ } + return; + } placeholderId = m.message_id; rotateTimer = setInterval(async () => { phraseIdx = (phraseIdx + 1) % SLOW_TOOL_PHRASES.length; @@ -1539,6 +1548,7 @@ async function withSlowToolIndicator(chatId: number, toolName: string, fn: () try { return await fn(); } finally { + done = true; clearTimeout(showTimer); if (rotateTimer) clearInterval(rotateTimer); if (placeholderId != null) {