Telegram plugin: dual-polling race condition when multiple Claude Code instances share one bot token

Resolved 💬 2 comments Opened Mar 21, 2026 by coopergee Closed Apr 18, 2026

Description

When multiple Claude Code instances (e.g., Desktop app + terminal CLI) are running simultaneously with the Telegram plugin enabled, each instance spawns its own MCP subprocess that calls bot.start() (grammy long-polling). Telegram's getUpdates API delivers each update to only ONE active polling connection. This causes one instance to silently consume all inbound messages while the other instance receives nothing — even though outbound reply/react/edit_message tools work fine from both.

This is a distinct bug from the tengu_harbor feature flag issue reported in #36431 and #36503. Even when channels are properly enabled, dual-polling will cause message loss.

Environment

  • Claude Code: 2.1.78 (Desktop) + terminal instance
  • OS: macOS Darwin 25.3.0
  • Plugin: telegram@claude-plugins-official v0.0.1
  • Runtime: Bun

Steps to Reproduce

  1. Open Claude Desktop app (which loads the Telegram plugin and spawns bun server.ts)
  2. Open a terminal Claude Code instance (which also loads the plugin and spawns a second bun server.ts)
  3. Send a message to the bot on Telegram
  4. Observe: one instance receives the update, the other gets nothing
  5. The starved instance can still send via the reply tool, but never receives inbound messages

Observable Symptoms

  • "Bun-2" visible in terminal when closing sessions — indicates a second bun process was spawned
  • Outbound works perfectly from both instances
  • Inbound silently fails on one instance with no error logged
  • typing... indicator may appear from the wrong instance's bot process
  • Persists across terminal restarts if the Desktop app (or an orphaned bun process) is still running

Root Cause

server.ts line 596 calls bot.start() unconditionally with no guard against concurrent pollers:

void bot.start({
  onStart: info => {
    botUsername = info.username
    process.stderr.write(`telegram channel: polling as @${info.username}\n`)
  },
})

Telegram's Bot API explicitly documents that only one getUpdates connection is allowed per bot token. A second connection either receives a 409 Conflict or silently loses the race for updates.

Proposed Fix

Add a lockfile (~/.claude/channels/telegram/bot.lock) that records the PID of the polling process. Before calling bot.start(), check the lock:

  • If locked by a living process → skip bot.start(), log a warning, but keep MCP tools functional
  • If locked by a dead process → take over the lock (stale lock cleanup)
  • On exit → release the lock

This ensures only one poller runs at a time while allowing multiple instances to still use outbound tools.

A local patch implementing this fix has been tested and is available if helpful.

Related Issues

  • #36431 — Telegram plugin: inbound MCP channel notifications not delivered to conversation
  • #36503 — --channels plugin shows "Channels are not currently available" but inbound notifications are ignored

Those issues track the tengu_harbor feature flag gate. This issue is the plugin-level dual-polling race, which is an independent bug that will cause message loss even when the feature flag is enabled.

---

🐛 Discovered by Will Scheffer & Claude (Opus 4.6), 2026-03-20.

🤖 Generated with Claude Code

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗