From 633a4443c2e5c1604ac57fdbc31c079bbbfc54a0 Mon Sep 17 00:00:00 2001 From: Paul Kloppers Date: Thu, 14 May 2026 00:08:23 +0200 Subject: [PATCH] ux(redstone): surface AI provider failure instead of silent drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When both OpenRouter AND Gemini return null (no key / 429 / 503 / network) and no tool ran during the turn, the handler used to silently return, leaving the user staring at their unanswered message wondering if the bot was broken. Real cause is usually the daily free-tier rate-limit exhausting + Gemini's intermittent 503s lining up. Now post a one-liner reply explaining: "🤖 AI is unreachable right now — both OpenRouter and Gemini just declined. Usually the free-tier daily cap resetting around UTC midnight. Try again in a few minutes, or use /model to pick a different model." Only fires when ranTool is false — if the model successfully called a tool earlier in the turn (image already sent, message posted, etc.) the user has already seen something visible and we keep the silent return for that case. Co-Authored-By: Claude Opus 4.7 --- bot/bot.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bot/bot.ts b/bot/bot.ts index fdaabc2..6d2e5d5 100644 --- a/bot/bot.ts +++ b/bot/bot.ts @@ -1653,7 +1653,21 @@ bot.on("message:text", async (ctx) => { } if (!reply) { - if (!ranTool) console.warn("redstone: no reply and no tool ran for", { chatId, text: text.slice(0, 80) }); + if (!ranTool) { + // Both providers returned null AND nothing useful happened during the + // turn — surface that to the user instead of silently dropping the + // message. Usually means OpenRouter hit its free-tier rate limit and + // Gemini is also down/throttled; tell them what to do. + console.warn("redstone: no reply and no tool ran for", { chatId, text: text.slice(0, 80) }); + try { + await ctx.reply( + "🤖 AI is unreachable right now — both OpenRouter and Gemini just declined. " + + "Usually the free-tier daily cap resetting around UTC midnight. Try again in a few minutes, " + + "or use /model to pick a different model.", + { parse_mode: "HTML", reply_parameters: { message_id: ctx.message.message_id } }, + ); + } catch { /* if even the error reply fails, nothing more we can do */ } + } return; }