Skip to content

Telegram plugin: polling loop exits permanently on network errors (no retry) #963

@DevAutoFarm

Description

@DevAutoFarm

Bug Description

The Telegram plugin's polling loop (server.ts, lines 959-993) only retries on 409 Conflict errors. Any other error (network errors like ECONNRESET, ETIMEDOUT, DNS failures, etc.) causes the polling loop to return permanently, killing Telegram connectivity for the rest of the session.

Steps to Reproduce

  1. Start a Claude Code session with the Telegram plugin (--channels plugin:telegram)
  2. Verify Telegram messaging works
  3. Change the Mac's network route (e.g., switch from USB tethering to LAN adapter, or change Wi-Fi networks)
  4. The getUpdates long-poll request fails with a network error
  5. Expected: Polling retries with backoff and recovers
  6. Actual: Polling loop exits permanently; Telegram is dead for the rest of the session

Root Cause

// server.ts, lines 977-993
} catch (err) {
  if (err instanceof GrammyError && err.error_code === 409) {
    // Only 409 retries ✅
    continue
  }
  if (err instanceof Error && err.message === 'Aborted delay') return
  process.stderr.write(`telegram channel: polling failed: ${err}\n`)
  return  // ← All other errors permanently exit the loop ❌
}

Impact

Users who switch network interfaces (e.g., VPN on/off, tethering mode changes, Wi-Fi roaming) lose Telegram connectivity permanently. The Claude Code session itself recovers, but the Telegram channel is dead. The only workaround is restarting the entire session, losing all conversation context.

Suggested Fix

Add retry-with-backoff for non-409 errors (similar to the existing 409 retry logic), with a maximum retry count to prevent infinite loops on fatal errors:

} catch (err) {
  if (err instanceof GrammyError && err.error_code === 409) {
    // existing 409 retry logic...
    continue
  }
  if (err instanceof Error && err.message === 'Aborted delay') return
  // Network errors — retry with backoff
  const MAX_NET_RETRIES = 30
  if (attempt <= MAX_NET_RETRIES) {
    const delay = Math.min(2000 * attempt, 30000)
    process.stderr.write(
      `telegram channel: polling failed (attempt ${attempt}/${MAX_NET_RETRIES}), retrying in ${delay / 1000}s: ${err}\n`,
    )
    await new Promise(r => setTimeout(r, delay))
    continue
  }
  process.stderr.write(`telegram channel: polling failed after ${MAX_NET_RETRIES} retries, giving up: ${err}\n`)
  return
}

Environment

  • Claude Code with plugin:telegram@claude-plugins-official v0.0.4
  • grammy ^1.21.0
  • macOS (Darwin 25.3.0)
  • Network change trigger: StealthTether USB tethering mode switch

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions