Эх сурвалжийг харах

Merge remote-tracking branch 'origin/kenneth/telegram-409' into kenneth/channels-rollup

# Conflicts:
#	external_plugins/telegram/server.ts
Kenneth Lien 5 сар өмнө
parent
commit
f3fc62a8e7

+ 33 - 12
external_plugins/telegram/server.ts

@@ -15,7 +15,7 @@ import {
   ListToolsRequestSchema,
   CallToolRequestSchema,
 } from '@modelcontextprotocol/sdk/types.js'
-import { Bot, InputFile, type Context } from 'grammy'
+import { Bot, GrammyError, InputFile, type Context } from 'grammy'
 import type { ReactionTypeEmoji } from 'grammy/types'
 import { randomBytes } from 'crypto'
 import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs'
@@ -628,14 +628,35 @@ bot.catch(err => {
   process.stderr.write(`telegram channel: handler error (polling continues): ${err.error}\n`)
 })
 
-bot.start({
-  onStart: info => {
-    botUsername = info.username
-    process.stderr.write(`telegram channel: polling as @${info.username}\n`)
-  },
-}).catch(err => {
-  // bot.start() only rejects if polling can't begin or dies unrecoverably —
-  // bad token, 409 conflict, network gone. Log it so the user isn't left
-  // wondering why messages stopped arriving.
-  process.stderr.write(`telegram channel: polling stopped: ${err}\n`)
-})
+// 409 Conflict = another getUpdates consumer is still active (zombie from a
+// previous session, or a second Claude Code instance). Retry with backoff
+// until the slot frees up instead of crashing on the first rejection.
+void (async () => {
+  for (let attempt = 1; ; attempt++) {
+    try {
+      await bot.start({
+        onStart: info => {
+          botUsername = info.username
+          process.stderr.write(`telegram channel: polling as @${info.username}\n`)
+        },
+      })
+      return // bot.stop() was called — clean exit from the loop
+    } catch (err) {
+      if (err instanceof GrammyError && err.error_code === 409) {
+        const delay = Math.min(1000 * attempt, 15000)
+        const detail = attempt === 1
+          ? ' — another instance is polling (zombie session, or a second Claude Code running?)'
+          : ''
+        process.stderr.write(
+          `telegram channel: 409 Conflict${detail}, retrying in ${delay / 1000}s\n`,
+        )
+        await new Promise(r => setTimeout(r, delay))
+        continue
+      }
+      // bot.stop() mid-setup rejects with grammy's "Aborted delay" — expected, not an error.
+      if (err instanceof Error && err.message === 'Aborted delay') return
+      process.stderr.write(`telegram channel: polling failed: ${err}\n`)
+      return
+    }
+  }
+})()